How does a constructor give two children (and a callback) a back reference to the parent, and when may it use them?
← Classes and OOP · Ref: Q2182
Give each child its own capture-assignment of 'this', one after another, and use none of them until the last one is set:
name: text lead :=? (team: this, role: "lead") with trait of Member as class ... deputy :=? (team: this, role: "deputy") with trait of Member as class ... announce :=? (team: this) is Announce as function (rtn: team.rollCall()) Stdout().println(`built: ${lead.introduce()}`)
THE RULE FOR MORE THAN ONE CHILD
A capture-assignment of 'this' (the parent link, Q2181) is allowed when every OTHER property is already set OR is itself the target of a LATER capture-assignment. So 'lead' may be created while 'deputy' and 'announce' are still unset, because both are about to be assigned the same way. The object becomes complete when the LAST of them ('announce') is set, and only from then on may the constructor call a child.
WHY THE ORDER MATTERS
A child holds the parent itself, and its methods may call back into the parent. 'lead.introduce()' reads 'team.name()' - harmless - but the parent's 'rollCall()' reads 'deputy', and a callback such as 'announce' runs 'rollCall()'. If the constructor called a child between the two assignments, that call could reach 'deputy' before it has a value: in Java that is a NullPointerException at run time. EK9 does not try to work out which calls happen to be safe; until the object is complete a property holding a capture of 'this' IS the object, so any call, pass or re-capture of it is E08061, and the fix is always the same - move it after the last child is set.
A DYNAMIC FUNCTION IS A CHILD TOO
'announce :=? (team: this) is Announce as function ...' is the same shape: creating it reads nothing, and its body runs only when it is called - calling it is the use, and that waits for completion. The order of the children does not matter (for example 'announce' could come first); what matters is that nothing is used before the last one is set.
See Q2183 for the mistakes this rule rejects, and Q1471 for an object holding itself directly ('me: this').
Example
defines module qa.oop.parent.link.two.children defines function Announce() as abstract <- rtn as String? defines trait Member introduce() as pure <- rtn as String? defines class Team name as String? lead as Member? deputy as Member? announce as Announce? default private Team() Team() -> text as String name: text lead :=? (team: this, role: "lead") with trait of Member as class override introduce() as pure <- rtn as String: `${role} of ${team.name()}` default operator ? deputy :=? (team: this, role: "deputy") with trait of Member as class override introduce() as pure <- rtn as String: `${role} of ${team.name()}` default operator ? announce :=? (team: this) is Announce as function (rtn: team.rollCall()) Stdout().println(`built: ${lead.introduce()}`) name() as pure <- rtn as String: name rollCall() as pure <- rtn as String: `${lead.introduce()}, ${deputy.introduce()}` announceNow() <- rtn as String: announce() default operator ? defines program TwoChildren() stdout <- Stdout() team <- Team("Rovers") stdout.println(team.announceNow())
Common mistakes
E08061 — 'lead' holds the team, and the team is not complete until 'deputy' and 'announce' are set - a child's method may call back into the parent and reach them. Use a child only after the last capture-assignment.
Incorrect:
Stdout().println(`built: ${lead.introduce()}`) deputy :=? (team: this, role: "deputy") with trait of Member as class override introduce() as pure <- rtn as String: `${role} of ${team.name()}` default operator ? announce :=? (team: this) is Announce as function (rtn: team.rollCall())
Correct:
deputy :=? (team: this, role: "deputy") with trait of Member as class override introduce() as pure <- rtn as String: `${role} of ${team.name()}` default operator ? announce :=? (team: this) is Announce as function (rtn: team.rollCall()) Stdout().println(`built: ${lead.introduce()}`)
Other ways to ask this
- How do I create two child objects that both reference their parent in the constructor?
- When can a constructor call a child that holds a parent reference?
- Can a callback registered in the constructor capture this?
- Why is calling the first child before the second is created E08061?
Coming from another language?
Java: children created with 'new Child(this)' in any order, and a child called halfway through the constructor can see a parent whose later fields are null; nothing checks. Kotlin: the same, with 'Leaking this' warnings. Swift: 'self' is unavailable until every stored property is set, so each child link is an optional assigned after phase one. EK9: every child is created as part of phase one, in order, and the compiler proves none is used before the last is in place.
Keywords: back reference, child, dynamic function, capture, callback, callback registered in constructor, dynamic class, parent reference, constructor, E08061, two children