After having dealt with late for a long time now and finding the two different aspects of the late modifier to be convoluted (see #1100 and #1101), I totally expected constructor initialization to not be lazy. But why is that?
You can find a more detailed version of the question and code here.
class Baz {
Baz(
// Implicit assignment by the caller.
this.assignmentInConstructor,
) : assignmentInInitializerList = calculate() {
assignmentInConstructorBody = calculate();
}
late final int assignmentInConstructor; // I expect this to be lazily initialized.
late final int assignmentInInitializerList; // I expect this to also be lazily initialized.
late final int assignmentInConstructorBody; // I expect this to not be lazily initialized.
}
None of the instance fields are lazily initialized. I included what I thought to be the intuitive though of someone who knows constructors but has not dealt with late yet.
In order to initialize assignmentInConstructor lazily you'd need to introduce something like call-by-name parameters (similar to Scala's def calculate(input: => Int) = input * 37). This may seem convenient, but it introduces the possibility that _any_ expression which is passed as a parameter could silently be turned into a lambda and evaluated zero, one, or more times, at any point in the future. In a language where side-effects are not frowned upon, I think that would be considered error prone.
Of course, if you want a parameter x to be evaluated lazily you can define it to have type T Function() rather than T, evaluate x() in the body rather than x, and pass () => e rather than e. It is less concise, but it will indicate the lazy evaluation explicitly at every call site.
assignmentInConstructorBody has the same issues, and I doubt that there are any languages with mutable state where a simple assignment can be evaluated lazily.
So laziness in this sense is probably not a good fit for Dart.
@eernstg So this means that this is completely intentional and going to stay this way?
@creativecreatorormaybenot Yes, I think this is not something that's going to change at this point.
I'm definitely of the opinion that it would be a very large and surprising change if assignments to initializing formals, in initializer lists, or in constructors or method method bodies were evaluated lazily (that is, if the RHS where thunked up and only evaluated on the next read). There are no other features in Dart that behave that way.
I also agree that re-using late on instance fields to mean "evaluate the initializer (if any) lazily" is not an entirely obvious choice, and will surprise some users. It's not entirely unprecedented, since top level variables and static fields are already evaluated lazily, so the overall behavior is not new to Dart, but using it on instance fields is new. We discussed this at length on the language team, debating between adding a new keyword for lazy fields vs re-using late with an initializer to mean evaluate this lazily. In the end, our consensus judgement was that using late was a good choice: it's not self-evident what it means, but it's fairly intuitive once you learn it (the initializer is just evaluated "late"), and it avoids adding another keyword that is only meaningful in a very small set of configurations. For better or worse, this was the call we made!
I'm going to close this issue out, since I don't think there's any further changes planned here, but feel free to follow up if you still have questions.
Most helpful comment
@creativecreatorormaybenot Yes, I think this is not something that's going to change at this point.
I'm definitely of the opinion that it would be a very large and surprising change if assignments to initializing formals, in initializer lists, or in constructors or method method bodies were evaluated lazily (that is, if the RHS where thunked up and only evaluated on the next read). There are no other features in Dart that behave that way.
I also agree that re-using
lateon instance fields to mean "evaluate the initializer (if any) lazily" is not an entirely obvious choice, and will surprise some users. It's not entirely unprecedented, since top level variables and static fields are already evaluated lazily, so the overall behavior is not new to Dart, but using it on instance fields is new. We discussed this at length on the language team, debating between adding a new keyword forlazyfields vs re-usinglatewith an initializer to meanevaluate this lazily. In the end, our consensus judgement was that usinglatewas a good choice: it's not self-evident what it means, but it's fairly intuitive once you learn it (the initializer is just evaluated "late"), and it avoids adding another keyword that is only meaningful in a very small set of configurations. For better or worse, this was the call we made!I'm going to close this issue out, since I don't think there's any further changes planned here, but feel free to follow up if you still have questions.