-
Notifications
You must be signed in to change notification settings - Fork 214
constructor initializer list — make expressions able to use earlier names in the list #1394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
We would like to see this too. I am one of the commenters on dart-lang/sdk#28950. The workarounds that we employ use additional private constructors. For your
This introduces a private constructor that the public constructor calls with computed values. Sometimes it's hard to follow what the computed values are so we may use a factory constructor:
We have mixed both styles and it can get messy when inheritance is also needed but at least with this private constructor workaround you can maintain the class's public interface. |
We already allow access to the value of initializing formals (not the variable itself, it's a new final variable). There is nothing fundamental preventing us from introducing a new variable for initializer list assignments too. It still doesn't allow you to introduce a temporary variable, say creating a pipe and storing its input and output in different variables, you have to store the reused object itself as a field. There is a trade-off between keeping the initializer list simple, and making it expressive. At the far end of expressiveness, we have Java where you can run any code to initialize the fields. Dart is closer to C++ in design. |
Simple is good. Then how about allowing the above
This concisely specifies the intent and is what the above workarounds accomplish. We heavily use this pattern in our reactive architecture where the parameter is a |
Dart initialization is order-independent. You can't see in which order fields are initialized because the fields aren't really initialized until every value is available. (Obviously expressions can have side-effects, so we can see the order of the initializing expressions, but that's a different thing). If we allow class C {
final int x;
final int y = x + 1; //current error: Only static members can be accessed in initializers.
final int z = y + 1; //current error: Only static members can be accessed in initializers.
C(this.x);
} should that then be based on source order or a computed dependency? That is, can I swap the lines around and it still works? That would be nice, so I guess we'd want that. We just compute the dependency graph between initializer expressions at compile-time, and evaluate the expressions in that order. Does that extend to the initializer list too? It probably should, so initializer list expressions are no longer evaluated in source order (unless totally unrelated via dependencies). And it's still not enough to allow, say: Piper() : var tmp = Pipe(), this.in = pipe.sink, this.out = pipe.stream; That would be neat - temporary local (final?) variables scoped for the (remainder of the) initializer list. Clever() : var tmp = ..., var _ = () { for (int i = 0; i < 100; i++) tmp = compute(tmp0; }(), ... tmp ...; We could allow any statement to occur in an initializer list then, comma-separated. At that point, we might as well do a full initializer block: Blocked() : {
var tmp = ...;
for (int i = 0; i < 100; i++) tmp = compute(tmp);
this.x = tmp.a;
this.y = tmp.b;
} {
actualBody();
} The block would not have access to class InitMe {
final out;
final in;
{
var pipe = Pipe();
in = pipe.sink;
out = pipe.stream;
}
} That would offer more generality, would definitely not work with const constructors, and would likely require a better "definite assignment" analysis. I think it's doable, it's just much more complicated than what we have now, so the question is, is it useful so often that it's worth the extra complication? |
My selfish enhancement request (and also dart-lang/sdk#28950) is to allow final initializers to access other final initializers. Nothing more complex than that. We build 90+% of our classes with all final properties - i.e. immutable. There is usually at least one value that isn't know until runtime (buffer, stream, etc.). These are the initialization parameters for the class. Once the initialization (declared as final) values are given to the class, the other final properties are able to be resolved relative to the given final. This is where the current Dart frustrates us - final initializers can't access other final values. Your
Your As to order of the initialization, I suggest following the same logic that Dart uses for
are allowed so it would seem that it's a computed dependency. BTW - I note that you use The |
@rich-j |
Check here for a concrete proposal, along with some arguments why we'd need to address a potentially massive breakage if we were to use it. |
@zoechi A functional style with concise property definition, including initialization, becomes a "mindset" (several years of Scala). It'd be great to have class property definitions such as:
Seeing the above in a class would immediately tell me that since Separating the initialization into constructors from the declaration increases the cognitive load required to understand the code. We do use factory constructors (they aren't inheritable) and multiple levels of named constructors to handle the initializer interdependencies. It just makes for convoluted constructor code. |
Something like that above example would likely be written with getters in Dart: final Map<String,dynamic> _jsonMap;
String get name => _jsonMap['name'];
ID get id => ID.fromString(_jsonMap['id']); That avoid storing three fields on each object when you only really need one and reduces memory load and churn. Same problem with the |
The class API communicates intent. Declaring a property as
where Early in converting our code to Dart we started using getters heavily, but we encountered challenges such as this Dart Angular example:
The Angular "reducing memory load and churn" sounds like premature optimization. Space/time tradeoff is always a consideration and is determined by system requirements and design. The |
I'd have to disagree on that particular reading of code. A getter without a setter means exactly the same thing, and you would have to check for the absence of a setter anyway, even if you declare the getter using a final field. Apart from that, then obviously different use-cases need different approaches, and your coding style seems to be doing a lot of computation up-front, which means caching it is reasonable, likewise anything depending on identity should be coded to respect that. |
Very similar to dart-lang/sdk#28950 |
Any news on this ? |
I try to initialize a Subject property, and a Stream property with some RX operators in the constructor but I struggle because I can't reuse my Subject variable in initializers. Factories are problematic too because I want inheritance. It's something that I was doing in Typescript, and I thought it was very basic. I removed the |
@rich-j Have you considered Dartpad example:
I realize this introduces a runtime check for each |
@larssn Yes, we are happily mostly using class LateTest {
final int count;
final Duration waitTime;
LateTest({required this.count, this.waitTime = const Duration(seconds: 1)});
late final Stream<int> stream1 = Stream.periodic(waitTime, (val) => val).take(count).asBroadcastStream();
late final Stream<String> stream2 = stream1.map((val) => "$val cookie${val > 1 ? 's' : ''}");
late final Stream<String> stream3 = stream1.map((val) => "$val l milk");
void start() {
stream2.listen(print);
stream3.listen(print);
}
}
main() {
LateTest(count: 3)..start();
} We mostly code using immutable objects that depend on provided external values (e.g. from network or database) that other values are derived from. As for performance it's always a tradeoff. Yes, |
+1 From a new Dart user's perspective, this definitely seems like a kink in the language. According to the documentation, the late keyword is intended for variables that are not initialized at the time of their declaration and variables that need lazy initialization. Both of these criteria don't fit well; what we'd really like to have is initialization at the time of declaration for variables that depend on each other. For me it comes down to a question of wording: "late" just doesn't fit for what I'm trying to achieve. It just feels wrong. |
@sebastianhaberey late final field = this.instanceField + 2; The whole State Management library I have created relies on this feature. |
Worth noticing that the suggestions to allow fields to access other fields, class C {
final int x = 1;
final int y = x + 1;
} are less viable with the introduction of the augmentation feature currently being design. The augmentation feature allows an augmenting declaration to override/augment the getter and setter of a field declaration, which means that That was actually already the case before. If class D {
int otherX = 42;
int get x => someRandom.nextBool() ? super.x : otherX;
} it's not clear that final int y = x + 1;
late final int z = x + 1; would behave differently for the exact same code. That's not great. With augmentations, it can be done inside the same class: class C {
final someRandom = Random();
int otherX = 42;
final int x = 1;
final int y = x + 1;
augment get x => someRandom.nextBool ? augmented : otherX;
} The reference to (I also want to refer to prior values in initializer lists. As late as today I did a workaround with a second constructor, just to be able to initialize what this feature would allow you to write as just : currentTime = (someExpression).millisecondsSinceEpoch, nextTime = currentTime - 1; So there is demand!) |
Uh oh!
There was an error while loading. Please reload this page.
In a constructor's initializer list, I'd like expressions to be able to use variables from the same list that were initialized earlier in the list.
I've read dart-lang/sdk#26655
and dart-lang/sdk#15346
So, I'm glad that it's possible to do this:
I would also like to be able to do this, which presently gives me an error "Only static members can be accessed in initializers."
I see in dart-lang/sdk#28950 that something similar was requested, and rejected with the rationale "We will definitely not make the initializer list have access to the real object.".
However, I don't want that as such. I'd like to have expressions later down the initializer list able to use initialized earlier in the list.
I meet this from time to time during Flutter development where I want to create a
StatelessWidget
, and so keep all of its membersfinal
, and doing this as part of the initializer for something related to initializing the widget seems to be cleaner than creating a method or getter to do the same.Also, it feels to me intuitive that it should work this way.
dart --version
)Dart VM version: 2.1.0-dev.4.0.flutter-cd9a42239f (Fri Sep 7 21:08:23 2018 +0000) on "macos_x64"
The text was updated successfully, but these errors were encountered: