The anatomy of a class
A class is its private properties, the constructors that give them values, the operator that says when it counts as 'set', and, when it needs one, a single definition of sameness. The compiler checks all four.
Why
A class in EK9 is built from four things, and the compiler checks each: what it holds, how an instance is first made, when an instance counts as 'set', and, if the class defines one, what 'the same' means. Nothing is left to convention, because convention is what fails under a deadline.
What it holds. Properties are always private, and a property written 'as T?' has no value until a constructor gives it one. The compiler then checks every constructor. A property that any public constructor leaves unset is an error (E08060); only a private constructor may leave properties unset, and it can then be used only from another constructor that completes the object. Where the properties have no initial value and the class has constructors of its own, the no-argument constructor the compiler would otherwise provide must be declared 'default private' (E07175), because a public one would hand out an Account with no owner.
What 'set' means. A class with properties must say when one of its objects counts as set: 'default operator ?' (set when every property is set), or an 'operator ?' of its own (E07235). EK9 will not guess, because every guard, every 'if x?' and every stream stage asks that question of every value.
Sameness is defined once. Choose '<=>' and the compiler generates '==', '<>', '<', '<=', '>' and '>=' from it; choose '==' when only equality makes sense and it generates '<>'. Writing them yourself, or asking for them with 'default operator', is E07181. Two comparisons that can disagree about the same pair of objects cannot be written, so they cannot be in the program.
A class is closed unless it says 'as open' (E05030). Extension is a design decision, not a default: an open class promises that every subclass can stand in for it, and EK9 asks for that promise to be made out loud.
None of this is style advice. It is the same conviction as the rest of EK9: a class that leaves a property unset, or disagrees with itself about equality, is not something to catch in review. It does not compile.
The correct form
#!ek9 defines module site.classes.anatomy defines class //A class is closed unless it says 'as open' - SavingsAccount below extends it. Account as open //Properties are always private; 'as T?' means "no value until a constructor gives it one". owner as String? balance as Integer? //The properties have no initial value, so a no-argument constructor must be private: //nothing outside the class can make an Account with no owner. default private Account() //A constructor's parameters go in one '->' block; 'this.x: x' copies each into its property. Account() -> owner as String balance as Integer this.owner: owner this.balance: balance //A method that changes the object. deposit() -> amount as Integer balance += amount //A method that returns a value: there is no 'return', the result is the '<-' variable. describe() as pure <- rtn as String: `${owner} holds ${balance}` //'?' (set when every property is set), '<=>' (sameness - '==', '<' and the rest are generated //from it), '#?' (goes with sameness) and '$' (the String form). default operator ? default operator <=> default operator #? default operator $ SavingsAccount extends Account rate as Integer? SavingsAccount() -> owner as String balance as Integer rate as Integer super(owner, balance) this.rate: rate //'override' is required to replace a method of the super class; 'super.' calls the original. override describe() as pure <- rtn as String: `${super.describe()} at ${rate}%` //A sub class with its own properties gives its own '?', '$' and sameness (with '#?'): //Account's cannot know 'rate', so each of these calls Account's first, then adds it. default operator ? default operator $ default operator <=> defines program ClassAnatomy() stdout <- Stdout() ann <- Account("Ann", 120) bob <- SavingsAccount("Bob", 80, 3) stdout.println(ann.describe()) stdout.println(bob.describe()) bob.deposit(40) stdout.println(bob.describe()) stdout.println($ann) twin <- Account("Ann", 120) carl <- Account("Carl", 50) stdout.println(`ann == twin: ${ann == twin}`) stdout.println(`ann < carl: ${ann < carl}`) stdout.println(`ann is set: ${ann?}`)
Output
Ann holds 120 Bob holds 80 at 3% Bob holds 120 at 3% Account(owner=Ann, balance=120) ann == twin: true ann < carl: true ann is set: true
Wrong forms
Each of these is written by people and by models, and each fails to compile - the error is what the compiler tells you.
Extending a class that is not open
Classes are closed by default; the base class must be declared 'as open' (or 'as abstract') before another class can extend it.
Fails with E05030: not open to be extended
#!ek9 defines module bad.site.classes.anatomy.closedbase defines class Account owner as String? default private Account() Account() -> owner as String this.owner: owner describe() as pure <- rtn as String: `Owned by ${owner}` default operator ? SavingsAccount extends Account SavingsAccount() -> owner as String super(owner)
Download classes_anatomy_closed_base.ek9
Read about E05030: not open to be extended
Uninitialised properties and no private default constructor
With a property that has no initial value, the no-argument constructor the compiler would provide is public and would leave it unset; declare 'default private Account()' so nothing outside the class can build an incomplete Account.
Fails with E07175: default constructor must be 'private' when uninitialised properties exist
#!ek9 defines module bad.site.classes.anatomy.publicdefault defines class Account owner as String? Account() -> owner as String this.owner: owner describe() as pure <- rtn as String: `Owned by ${owner}` default operator ?
Download classes_anatomy_public_default.ek9
Read about E07175: default constructor must be 'private' when uninitialised properties exist
Requesting '==' beside '<=>'
A type defines sameness once, with '<=>' or '=='; '==', '<>', '<', '<=', '>' and '>=' are then generated from it and are never written or requested.
Fails with E07181: comparisons with the type itself are generated from its sameness ('<=>' or '=='), never written or requested
#!ek9 defines module bad.site.classes.anatomy.writtenequality defines class Account owner as String? default private Account() Account() -> owner as String this.owner: owner describe() as pure <- rtn as String: `Owned by ${owner}` default operator ? default operator <=> default operator == default operator #?
Download classes_anatomy_written_equality.ek9
Read about E07181: comparisons with the type itself are generated from its sameness ('<=>' or '=='), never written or requested
A class with properties and no operator ?
A class with properties must say what 'set' means for its objects: add 'default operator ?' (every property set) or write 'override operator ?'.
Fails with E07235: aggregate with properties/fields requires 'override operator ?' (custom) or 'default operator ?' (auto-generated) to define set/unset semantics
#!ek9 defines module bad.site.classes.anatomy.noisset defines class Account owner as String? default private Account() Account() -> owner as String this.owner: owner describe() as pure <- rtn as String: `Owned by ${owner}`
Download classes_anatomy_no_isset.ek9
Read about E07235: aggregate with properties/fields requires 'override operator ?' (custom) or 'default operator ?' (auto-generated) to define set/unset semantics
A constructor that leaves a property without a value
Every constructor must give every 'as T?' property a value; a property one constructor forgets is read unset later.
Fails with E08060: is/may not be initialised before use
#!ek9 defines module bad.site.classes.anatomy.unsetproperty defines class Account owner as String? balance as Integer? default private Account() Account() -> owner as String this.owner: owner describe() as pure <- rtn as String: `${owner} holds ${balance}` default operator ?
Download classes_anatomy_unset_property.ek9
Read about E08060: is/may not be initialised before use