Constructors: building an object in two phases
An object is built in two phases: first every property gets a value, then the object is complete and may be used. Constructors, this(...), super(...) and private constructors are shaped to keep the phases apart.
Why
Building an object has two phases. In the first, every property is given a value: the super class constructor runs, then this class's property initialisers in the order they are declared, then the constructor body. In the second, the object is complete, and only then may anything use it. EK9 keeps the phases apart, so a half-built object can never be seen.
Everything about constructors follows from that. A constructor hands its work to another with 'this(...)' or 'super(...)' only as its first act, so nothing runs against an object whose super class has not been built. A constructor is judged through its 'this(...)' chain, so a private constructor that leaves a property unset can only be reached through 'this(...)' from one that sets it, never by a construction call (E08264). A constructor with no 'super(...)' relies on the super class's no-argument constructor, so when that one is private it must call one it can reach (E07176), and a sub class with no constructor at all needs a super constructor it can call (E07177).
It is also why calling a method, or handing out 'this', before every property is set is E08061. In other languages the same mistake reads a field that has not been given its value yet, and quietly gets its default (null or zero). In EK9 it does not compile.
A child that genuinely needs a reference back to the parent that creates it has one way to be given it while the parent is still being built, the parent link. (Once the parent is complete it may hand itself out like any other object.) The parent link has its own questions and answers, linked under Related, rather than being repeated here.
The correct form
#!ek9 defines module site.classes.constructors defines constant noReading <- 0 defines class Reading as open //A property initialiser runs before ANY constructor body, so 'label' is "reading" there. label as String: "reading" amount as Integer? //A no-argument constructor that delegates with this(...) rather than repeating the work. Reading() this(noReading) Reading() -> amount as Integer this.amount: amount label += `: ${amount}` //A private constructor is for the class's own use; it delegates first, so every property is set. private Reading() -> label as String amount as Integer this(amount) this.label: `${label}: ${amount}` relabelled() -> newLabel as String <- rtn as Reading: Reading(newLabel, amount) describe() as pure <- rtn as String: label default operator ? default operator $ Sample extends Reading units as String? //Its own property has no initial value, so its default constructor must be private. default private Sample() //super(...) runs the Reading constructor (and Reading's initialisers) before this body. Sample() -> amount as Integer units as String super(amount) this.units: units override describe() as pure <- rtn as String: `${super.describe()} ${units}` default operator ? default operator $ defines program Constructors() stdout <- Stdout() stdout.println(Reading().describe()) stdout.println(Reading(21).describe()) stdout.println(Reading(21).relabelled("indoor").describe()) stdout.println(Sample(7, "mm").describe())
Download classes_constructors.ek9
Output
reading: 0 reading: 21 indoor: 21 reading: 7 mm
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.
Relying on a private super constructor
A constructor with no super(...) call relies on the super class's no-argument constructor; when that is private, call an accessible super(...) constructor instead.
Fails with E07176: a constructor cannot rely on the implicit super() - the super has no accessible no-argument constructor (it is 'private', or there is none) - call or provide an accessible initialising super constructor
#!ek9 defines module bad.site.classes.constructors.privatesuper defines class Reading as open amount as Integer? default private Reading() Reading() -> amount as Integer this.amount: amount default operator ? Sample extends Reading units as String? Sample() -> units as String this.units: units default operator ?
Download classes_constructors_private_super.ek9
Read about E07176: a constructor cannot rely on the implicit super() - the super has no accessible no-argument constructor (it is 'private', or there is none) - call or provide an accessible initialising super constructor
A sub class with no constructor when the super class constructor is private
With no constructor of its own, Sample would be given a no-argument one that calls the super class's no-argument constructor, and that one is private. Write a constructor for the sub class that calls a super constructor it can reach.
Fails with E07177: the super type has no accessible no-argument constructor (it is 'private', or there is none), so a default constructor cannot be generated here - you must define a constructor for this sub-type that calls an accessible super(...)
#!ek9 defines module error.e07177.bad defines class Reading as open amount as Integer? default private Reading() Reading() -> initial as Integer this.amount: initial default operator ? //No constructor, and none can be generated: it would have to call the private Reading() Sample extends Reading describe() <- rtn as String: "a sample"
Read about E07177: the super type has no accessible no-argument constructor (it is 'private', or there is none), so a default constructor cannot be generated here - you must define a constructor for this sub-type that calls an accessible super(...)
Calling a method before every property is set
A constructor that calls a method of its own object before every property has a value is using a half-built object. In Gauge, twice() reads 'reading' before it is set, and 'doubled', the property being assigned, has no value yet either, so setting 'reading' first does not help. Every property, the one being assigned included, needs a value before the method is called: give 'doubled' an initial value ('doubled as Integer: 0') and set 'reading' before calling twice(), as the fix on the E08061 page does. In Document, the section captures 'this' before 'title' is set. Set 'title' first; then handing out 'this' to the section is the parent link.
Fails with E08061: the object is used before it is fully constructed - set every property before calling its methods
#!ek9 defines module error.e08061.bad defines trait Part describe() as pure <- rtn as String? defines class Gauge reading as Integer? doubled as Integer? Gauge() doubled: twice() reading: 21 twice() <- rtn as Integer: reading * 2 default operator ? Document title as String? section as Part? default private Document() Document() -> text as String section :=? (owner: this) with trait of Part as class override describe() as pure <- rtn as String: `a section of ${owner.name()}` default operator ? title: text name() as pure <- rtn as String: title default operator ?
Read about E08061: the object is used before it is fully constructed - set every property before calling its methods
Constructing through a private constructor that leaves a property unset
A constructor is judged through its this() chain: a private one that leaves a property unset can only be reached through this(...) from a constructor that sets it, never by a construction call.
Fails with E08264: this constructor leaves properties without a value - it can only be used through this() from a constructor that sets them; construct through a constructor that sets every property
#!ek9 defines module bad.site.classes.constructors.privatepartial defines class Reading label as String? amount as Integer? default private Reading() Reading() -> label as String amount as Integer this.label: label this.amount: amount private Reading() -> label as String this.label: label relabelled() -> newLabel as String <- rtn as Reading: Reading(newLabel) default operator ?
Download classes_constructors_private_partial.ek9
Read about E08264: this constructor leaves properties without a value - it can only be used through this() from a constructor that sets them; construct through a constructor that sets every property