-
Notifications
You must be signed in to change notification settings - Fork 213
Expressions that are "as constant as possible" #4084
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
Could this be expanded to allow This would allow data classes that should never preoccupy themselves with canonicalization issues to advertise they should be const when possible, without the developer having to add Of course this then creates the issue that a constructor might be |
A user on discord ( |
That's an interesting idea! It might be possible. It might also be a bit too implicit (in the sense that we'll end up having some lists that are constant "by accident", even though they must actually be mutated at some point). But it's certainly worth keeping in mind.
Indeed, that's another reason why it might be a bit too aggressive to have a
Right. We can't say |
Can you add I'll assume it's only a replacement for That would put the reast of that expression in a Then we use the same rules as for constant expressions today, with small twists:
So effectively we pass down a flag on the There are expressions where a sub-expression doesn't have to be constant, if it's in a branch not taken. const c = true ? "banana" : (1 ~/ 0); Here If we do: var v = const? [bool.hasEnvironment("override") ? int.fromEnvironment("override") : computeValue()]; then the expression is not "syntactically a constant expression", which is a bad concept anyway. var v = [const bool.hasEnvironment("override") ? const int.fromEnvironment("override") : computeValue()]; I think this can be built into the existing rules with fairly little work. The biggest issue would be existing assumptions and bad definitions (and the lack of actually defining "constant evaluation" and when it happens, making |
1 similar comment
Can you add I'll assume it's only a replacement for That would put the reast of that expression in a Then we use the same rules as for constant expressions today, with small twists:
So effectively we pass down a flag on the There are expressions where a sub-expression doesn't have to be constant, if it's in a branch not taken. const c = true ? "banana" : (1 ~/ 0); Here If we do: var v = const? [bool.hasEnvironment("override") ? int.fromEnvironment("override") : computeValue()]; then the expression is not "syntactically a constant expression", which is a bad concept anyway. var v = [const bool.hasEnvironment("override") ? const int.fromEnvironment("override") : computeValue()]; I think this can be built into the existing rules with fairly little work. The biggest issue would be existing assumptions and bad definitions (and the lack of actually defining "constant evaluation" and when it happens, making |
That's my starting point, but if it turns out to be useful to allow it in additional positions then we can scrutinize the grammar and see if it can be done. Allowing
That seems less intuitive to me. I'm thinking of A ' |
Simple enough: don't make the list constructors I would see |
Extending this to declarations, it could look like I'm also wondering if pushing |
It seems there are many people who don't like Particularly, I think it's a costless optimization. Even if it may be negligible for many cases, it's basically costless, except for a little bit of verbosity, and this is exactly what this issue proposes to solve. There's an alternative approach. We could make something similar to what was proposed in #1410, but instead of making everything Then, we would have to revert Is this better than what we currently have? I'm not sure. We would have to extract some data to have a better idea which case is more common. In my personal experience with Flutter, we would have much less However, even if the data says that this is also true for non-Flutter Dart, it is an extremely breaking change to introduce in the language now. |
@mateusfccp I wondered about that too
The first argument against const by default was that people would not see if something is not "consted". This is solved by const? by default, as if you want errors if an expression is not a const, you can still use The second issue with const by default, and admittedly the bigger one was the identity issue. Const? by default could be implemented in such a way that every declaration that is not void main () {
print(buildFoo() == buildFoo());
}
buildFoo() {
return MyConstConstructor();
} But this would not: void main () {
print(buildFoo() == buildFoo());
}
buildFoo() {
final foo = MyConstConstructor();
return foo;
} Still a dangerous change but maybe one worth investigating ? I don't know, just an idea.
Well it's not costless at the moment, that's why this proposal exists |
I like the fact that there's Still, I'm not sure I've understood how this proposal helps with declarations. That's half of the times I encounter the "churn" problem. Example: class Point {
const Point(this.x, this.y);
final double x;
final double y;
}
const x = 2.0;
const y = 3.0;
// can be const, so the auto fix will eventually make this `const` on save
final p = Point(x, y); At this point I save and the lint successfully turns // ...
const p = Point(x, y); // auto fixed Then, I need to change import 'dart:math';
// ...
const x = 2.0;
const y = Random().nextDouble();
const p = Point(2, 3); // error So In other words, if I understood this proposal correctly, when performance is a must, the developer must avoid |
@lucavenir, did you mean You could use the following if you want the best performance, and you don't want to touch the declaration of final p = const? Point(x, y); This would make Of course, you could also declare
Well, if you insist that a given expression must be a constant expression then you must use But otherwise it's the other way around: You would use |
Yes, sorry! I'll edit my comment to make it clearer. |
I would hope that a const? Point(this.x, this.y); For the use-case of Flutter widget trees, framework users would almost never have to write See #3399 |
After all this discussion, I'm still not sure I see the benefits of
Given that the original "problem" was the chore of needing to add |
I somewhat disagree with this.
|
I adjusted the proposal to include |
about
It would be more useful if a Otherwise in flutter, in the build method, The implications are the same anyway, concerning potential bugs this could introduce.
This looks more dangerous than if Dart was designed from the get go with a |
Oh, that's a great point! So we'd want to express the rule that all subexpressions of a given constructor invocation should be treated as if they were in scope of a One gnarly problem is the syntax: I to think that a constructor declaration like We could also consider having support for a We would then be able to express the behavior that you mention by adding |
I added a proposal for |
The language team has had discussions about the need to change many parts of an expression in response to a small modification that makes a previously constant subexpression non-constant, or vice versa. For example:
This causes a lot of inconveniences in daily development because it's a non-trivial editing operation to make "as much as possible" constant in a given expression when we can't just say "all of it".
Arguably, the process is even more tricky in the other direction, because we can't see locally whether or not the list is expected to be modified in the future.
A more subtle semantic difference is the behavior of
identical
, where constant expressions yield canonicalized instances, which means thatconst <int>[]
is the same object as another occurrence of the syntaxconst <int>[]
, whereas<int>[]
and<int>[]
are two distinct objects (assuming that they aren't constant based on aconst
modifier on some enclosing expression), even in the case where we are considering two distinct evaluations of the same expression in the source code.Proposal:
const?
expressionsWe introduce the expression modifier
const?
, which amounts to a request that every subexpression of the expression that carries this modifier is constant, if possible. The grammar changes as follows:The point is that it is much safer for an automatic mechanism to introduce
const
modifiers on a set of expressions if this operation is justified by an explicit request in the source code. We could say that the developer promises that it won't be a bug to implicitly addconst
to any of the subexpressions of the expression that has the modifierconst?
, nor will it be a bug if no such modifier is added.The example above then becomes simpler:
This would largely eliminate the churn which is caused by changes to the constness of subexpressions of a bigger expression.
We define the semantics of the
const?
modifier in terms of constant capable expressions. On the target expression, the mechanism would addconst
modifiers implicitly, in a bottom-up traversal, such that every expression gets theconst
modifier if and only if it is constant capable.const
. For example,const A()
orconst <int>[]
.C<int>(1)
.e1 + e2
,!e3
,e4 ? e5 : e6
) is constant capable if the operands are constant capable, and if it yields a constant expression whenconst
is added (where it can be added syntactically) in a bottom-up traversal of the expression, to every subexpression which is constant capable. Similarly for other composite expressions (that is, expressions whose constness depends on the constness of its subexpressions, and possibly other criteria like the actual value or its type).With this mechanism in place, it would be possible, for example, for a Flutter developer to put a
const?
at the outermost level of a large expression creating a widget tree. This expression would then be a constant expression "as far as possible". This would amount to the same thing as addingconst
at the top, or addingconst
to any number of children, depending on the ability of each subtree of the widget tree to be constant or not. Crucially, there's no need to change anything about constness in the entire expression when some tiny subexpression is changed from constant to non-constant; we just proceed to make that change, and the constness of the entire expression will then implicitly be modified as needed.Proposal:
const?
constructors@abitofevrything introduced the following idea.
A constructor can have the modifier
const?
. The grammar changes as follows:Exactly the same compile-time errors are raised for such a constructor declaration as would be the case if it had had the modifier
const
.A constructor that has the modifier
const?
is a constant constructor. This implies that it can be invoked with theconst
modifier, yielding a constant expression, subject to all the usual checks to confirm that said expression is indeed a correct constant expression. Also,const
can be omitted when the constructor is invoked in a constant context, just like other constant constructors.Assume that
e
is an instance creation expression that invokes aconst?
constructor k. Ife
does not start withconst
ornew
, ande
does not occur in a constant context thene
is treated asconst? e
.This implies that invocations of this constructor will always be "as constant as possible", except that an
e
of the formconst e1
requires the invocation to be a constant expression, no ifs or buts, andnew e1
requires the invocation to be non-constant. Note thatnew
has the same effect as removingconst?
from the constructor, in the sense that it also cancels the automatic conversion of subexpressions to constant expressions, unless they have their own reason te become constant, e.g., by having their ownconst
modifier, or being invocations of aconst?
constructor, etc.Proposal:
const?
parametersA formal parameter declaration in a function declaration would be able to have the modifier
const?
. The effect would be that every actual argumenta
which is passed to this parameter in a situation where the statically known signature of the function has this modifier on the given parameter will be treated asconst? (a)
. In other words, every actual argument passed to this parameter will be "as constant as possible".The grammar changes as follows:
Discussion
A fundamental property of this proposal is that it makes the request for constant expressions more flexible ("just write
const?
at the outermost level, and the compiler/analyzer will sort it out"), but it preserves the property that it takes a human expression of intent to make an expression constant.With the first proposal (supporting
const? e
wheree
is an expression), the expression of intent is local: It is always possible to see the keywordconst?
somewhere in the physically enclosing source code.The second proposal is intended to be an addition to the first proposal, not a replacement. It is more aggressive than the first one, in the sense that it will make some expressions constant without any locally visible indication.
The second proposal relies on the assumption that there will be some classes (or at least some constructors of some classes) whose semantics is such that it is always OK to choose to make the constructor arguments constant as far as possible, because there are no bugs which can be caused by making the choice to turn some of those constructor arguments and/or their subexpressions into constant expressions, and there are no bugs which can be caused by not turning them into constant expressions. This is a heavy burden to lift for the authors and maintainers of that constructor, and the enclosing class, and its collaborators, but it might work quite well in practice. The community will have to develop a culture around how to use this feature well.
Conversely, the notion of a
const?
constructor will allow code that creates instances of these classes to obtain a maximal amount of const-ness in client code, with no effort (writing or maintaining) from the client developer.Finally, the third proposal allows us to specify that every actual argument
a
to a specific formal parameter must be treated asconst? (a)
, which means that we make "constness" the default for those actual arguments.The second and third proposals are somewhat overlapping: If we choose to make a constructor
const?
then the constructor must also itself be a constant constructor, and every actual argument passed in an invocation of that constructor is automatically made "as constant as possible". We can achieve very much the same effect by marking every formal parameter of the same constructor as aconst?
parameter. This means that every subexpression will be "as constant as possible", but the invocation of this constructor itself will not be constant (unless there is some other reason why it is requested to be constant).const?
formal parameters can be parameters of any function, including static and instance methods, local functions, function literals, etc. This means that they are a much broader mechanism thanconst?
constructors.Finally, note that function types do not support marking a parameter as
const?
, it is a mechanism which is only available for invocations of function declarations, and it has no effect on invocations of function objects, nor on dynamic invocations.Versions
const?
parameters.const?
constructors. Change the phrase 'evidently constant' to 'constant capable'.The text was updated successfully, but these errors were encountered: