It doesn't appear possible to fully initialise the fields of a sub-class using an it-block in the constructor without making a second call to the supplied closure.
Given the code (the key difference being the additional f.call(this) inside of Sub2):
Which shows that in the first case (Sub1), the desc field is initialised, but the vals field is not. In the second case (Sub2), both fields are initialised but the closure is executed twice (which is not really an issue for my code, but not hard to see how it could be dangerous/expensive).
Further, making the it-block in Sup nullable |This|? f := null results in a FieldNotSetErr from Sup.make.
Since the "simple" desc field is set in both cases, it looks like a bug in initialising complex objects using the it-block constructor?
M
vkuzkokovTue 1 Mar 2011
Construction as it works now has understandable (I still wouldn't dare to say simple) semantics: super-constructor, field initializers, constructor body, special checks. But some cases it works not as it should from the first glance.
Now, that's Obj? cast is something that doesn't seem too right.
brianTue 1 Mar 2011
It is definitely an interesting case that proves how good intentioned compile time and runtime checks can get in the way.
Ideally you should ensure that the it-block constructor is never run more than once. As a community we should probably establish a convention who is in charge of the it-block - the base class or the subclass? If the base class is responsible then the subclasses should never call the it-block. If the subclass is responsible then the base class should accept null to delegate to the subclasses.
By convention I think it-blocks should always be handled by base classes. That way if you see your super class takes an it-block, then you shouldn't double invoke it. That seems the simplest, most direct convention. In fact I noticed a change Andy pushed to Dialog I was going to go back and look at that has this problem.
If the base class handles the it-block, then you have field initializers in subclasses overriding what the it-block set. The sort of hacky work around is to cast the field to (Obj?) and check if it has already been set before assigning as suggested by @vkuzkokov.
If the subclass handles the it-block, then you have null field checks in base classes running before the it-block has gotten a chance to run. The work around there is to set your non-nullable fields to dummy values (but you loose some of the runtime checks to ensure it was set).
In both cases the fundamental problem is that we do full lifecycle at each class level before moving to subclasses, but it-blocks span this process. It is tricky a problem and I am not sure I can propose any specific solutions that wouldn't require a fairly complicated change to how object construction works (which in turn would create more complicated stuff developers would have to understand).
msl Tue 1 Mar 2011
It doesn't appear possible to fully initialise the fields of a sub-class using an it-block in the constructor without making a second call to the supplied closure.
Given the code (the key difference being the additional
f.call(this)
inside of Sub2):The output is:
Which shows that in the first case (Sub1), the desc field is initialised, but the vals field is not. In the second case (Sub2), both fields are initialised but the closure is executed twice (which is not really an issue for my code, but not hard to see how it could be dangerous/expensive).
Further, making the it-block in Sup nullable
|This|? f := null
results in aFieldNotSetErr
fromSup.make
.Since the "simple"
desc
field is set in both cases, it looks like a bug in initialising complex objects using the it-block constructor?M
vkuzkokov Tue 1 Mar 2011
Construction as it works now has understandable (I still wouldn't dare to say simple) semantics: super-constructor, field initializers, constructor body, special checks. But some cases it works not as it should from the first glance.
Here's workaround I could think of:
Now, that's
Obj?
cast is something that doesn't seem too right.brian Tue 1 Mar 2011
It is definitely an interesting case that proves how good intentioned compile time and runtime checks can get in the way.
Ideally you should ensure that the it-block constructor is never run more than once. As a community we should probably establish a convention who is in charge of the it-block - the base class or the subclass? If the base class is responsible then the subclasses should never call the it-block. If the subclass is responsible then the base class should accept null to delegate to the subclasses.
By convention I think it-blocks should always be handled by base classes. That way if you see your super class takes an it-block, then you shouldn't double invoke it. That seems the simplest, most direct convention. In fact I noticed a change Andy pushed to Dialog I was going to go back and look at that has this problem.
If the base class handles the it-block, then you have field initializers in subclasses overriding what the it-block set. The sort of hacky work around is to cast the field to (Obj?) and check if it has already been set before assigning as suggested by @vkuzkokov.
If the subclass handles the it-block, then you have null field checks in base classes running before the it-block has gotten a chance to run. The work around there is to set your non-nullable fields to dummy values (but you loose some of the runtime checks to ensure it was set).
In both cases the fundamental problem is that we do full lifecycle at each class level before moving to subclasses, but it-blocks span this process. It is tricky a problem and I am not sure I can propose any specific solutions that wouldn't require a fairly complicated change to how object construction works (which in turn would create more complicated stuff developers would have to understand).