Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(204)

Side by Side Diff: docs/language/informal/optional-new-const.md

Issue 2929753003: Add informal specification for optional-new/const, constructor tearoffs. (Closed)
Patch Set: Add more text Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Optional new/const
2
3 Author: Lasse R.H. Nielsen ([lrn@google.com](mailto:lrn@google.com))
4
5 Version: 0.9 (2017-06-20)
6
7 Status: Under discussion
8
9 This informal specification documents a group of four related features.
10 * Optional `const`
11 * Optional `new`
12 * Constructor tear-offs
13 * Potentially constant auto-`new`/`const`.
14
15 These are ordered roughly in order of priority and complexity. The constructor t ear-offs feature effectively subsumes and extends the optional `new` feature.
16
17 ## Optional const (aka. "const insertion")
18
19 In current Dart code, every compile-time constant expression (except for annotat ions) must be prefixed with a `const` keyword. This is the case, even when the c ontext requires the expression to be a compile-time constant expression.
20
21 For example, inside a `const` list or map, all elements must be compile-time con stants. This leads to repeated `const` keywords in nested expressions:
22
23 ```dart
24 const dictionary = const {
25 "a": const ["able", "apple", "axis"],
26 "b": const ["banana", "bold", "burglary"],
27
28 };
29 ```
30
31 Here the `const` on the map and all the lists are *required*, which also means t hat they are *redundant* (and annoying to have to write).
32
33 The "optional const" feature allows you to omit the `const` prefix in places whe re it would otherwise be required. It is effectively optional.
34
35 The feature can also be seen as an "automatic const insertion" feature that auto matically inserts the missing `const` where it's necessary. The end effect is th e same - the user can omit writing the redundant `const`.
36 This is somewhat precedented in that metadata annotations can be written as `@Fo o(constantArg)`.
37
38 Making `const` optional intersects perfectly with the "optional new" feature bel ow, which does the same thing for `new`.
39
40 Currently, the `const` prefix is used in front of map literals, list literals an d *constructor calls*.
41 Omitting the `const` prefix from list and map literals does not introduce a need for new syntax, since that syntax is already used for plain list and map litera ls.
42 That doesn't apply to un-prefixed constructor calls – those do introduce a synta x that isn't currently allowed:
43 `MyClass<SomeType>.name(arg)`. The language allows generic function invocation, which covers the unnamed constructor call `MyClass<SomeType>(arg)`, but it doesn 't allow applying type parameters to an identifier and *not* immediately calling the result.
44
45 To allow all const constructor invocations to omit the `const`, the grammar need s to be extended to handle the case of `MyClass<SomeType>.name(arg)`.
46 This syntax will only apply to unprefixed constructor invocations (at least unle ss we also introduce type-instantiated generic method tear-offs).
47
48 ### Prior discussion
49 See http://dartbug.com/4046 and https://github.com/lrhn/dep-const/blob/master/pr oposal.md
50
51 The syntax for a constructor call is less ambiguous now than when these proposal s were drafted, because generic methods have since been added to the language. T he language has already decided how to resolve parsing of the otherwise ambiguou s `Bar(Foo<int, bar>(42))`.
52
53
54 ### Informal specification
55
56 * An expression occurs in a "const context" if it is
57 * a literal const `List`, const `Map` or const constructor invocation (`co nst {...}`, `const [...]`, `const Symbol(...)`, `@Symbol(...)`),
58 * a parameter default value,
59 * the initializer expression of a const variable,
60 * a case expression in a switch statement, or
61 * is a sub-expression of an expression in a const context.
62
63 That is: `const` introduces a const context for all its sub-expressions, as do the syntactic locations where only const expressions can occur.
64
65 * If a non-const `List` literal, non-const `Map` literal or invocation express ion (including the new generic-class-member notation) occurs in a const context, it is equivalent to the same expression with a `const` in front. That is, you d on't have to write the `const` if it's required anyway.
66 That is, an expression on one of the forms:
67 * `Foo(args)`
68 * `Foo<types>(args)`
69 * `Foo.bar(args)`
70 * `Foo<types>.bar(args)`
71 * `prefix.Foo(args)`
72 * `prefix.Foo<types>(args)`
73 * `prefix.Foo.bar(args)`
74 * `prefix.Foo<types>.bar(args)`
75 * `[elements]`
76 * `{mapping}`
77
78 becomes valid in a `const` context.
79
80 * The grammar is extended to allow `Foo<types>.id(args)` and `prefix.Foo<typeA rguments>.id(args)` as an expression. They would not otherwise be valid expressi ons anywhere in the current grammar. They still only work in a const context (it 's a compile-time error if they occur elsewhere, just not a grammatical error).
81
82 * Otherwise this is purely syntactic sugar, and existing implementations can h andle this at the syntactic level by inserting the appropriate synthetic `const` prefixes.
83
84
85 ## Optional new (aka. "new insertion")
86
87 Currently, a call to a constructor without a prefixed `new` (or `const`) is inva lid. With the optional const feature above, it would become valid in a const con text, but not outside of a const context.
88
89 So, if the class `Foo` has a constructor `bar` then `Foo.bar()` is currently a s tatic warning/runtime failure (and strong mode compile-time error).
90
91 Like for "optional const", we now specify such an expression to be equivalent to `new Foo.bar()` (except in a const context where it's still equivalent to `cons t Foo.bar()`).
92
93 The "empty-named" constructor also works this way: `Foo()` is currently a runtim e-error, so we can change its meaning to be equivalent to `new Foo()`.
94
95 Like for optional const, we need to extend the grammar to accept `List<int>.fill ed(4, 42)`.
96
97 The `new` is optional, not prohibited. It may still be useful to write `new` as documentation that this actually creates a new object. Also, some constructor na mes might be less readable without the `new` in front.
98
99 In the longer run, we may want to remove `new` so there won't be two ways to do the same thing, but whether that is viable depends on choices about other featur es that we are considering.
100
101 Having optional `new` means that changing a static method to be a constructor is not necessarily a breaking change. Since it's only optional, not disallowed, ch anging in the other direction is a breaking change.
102
103 ### Prior discussion
104
105 See: http://dartbug.com/5680, http://dartbug.com/18241, http://dartbug.com/20750 .
106
107 ### Informal specification
108
109 * An expression on one of the forms:
110 * `Foo(args)`
111 * `Foo<types>(args)`
112 * `Foo.bar(args)`
113 * `Foo<types>.bar(args)`
114 * `prefix.Foo(args)`
115 * `prefix.Foo<types>(args)`
116 * `prefix.Foo.bar(args)`
117 * `prefix.Foo<types>.bar(args)`
118
119 where `Foo`/`prefix.Foo` denotes a class and `bar` is a named constructor of the class, and that is not in a const context, are no longer errors.
120
121 * Instead they are equivalent to the same expression with a `new` in front. Th is makes the `new` optional, but still allowed.
122 * The grammar allows `prefix.Foo<typeArguments>.bar(args)` and `Foo<typeArgume nts>.bar(args)` as expressions everywhere, not just inside const contexts. These are not valid syntax in the current grammar.
123 * Otherwise this is purely syntactic sugar, and existing implementations can h andle this at the syntactic level by inserting a synthetic `new` in front of non -const expressions that would otherwise try to invoke a constructor. This is sta tically detectable.
124
125 ## Constructor tear-offs
126
127 With constructors being callable like normal static functions, it makes sense to also allow them to be *torn off* in the same way. If `Foo.bar` is a constructor of class `Foo`, then the *expression* `Foo.bar` will be a tear-off of the const ructor (it evaluates to a function with the same signature as the constructor, a nd calling the function will invoke the constructor with the same arguments and an implicit `new`, and return the result).
128
129 The tear-off of a constructor from a non-generic class is treated like a tear-of f of a static method - it's a compile-time constant expression and it is canonic alized. A generic class constructor tear-off is treated like the tear-off of an instance method. It is not a compile-time constant and it isn't required to be c anonicalized, but it must still be *equal* to the same constructor torn off the same class instantiated with the same type parameters.
130
131 For a non-named constructor, the expression `Foo` already has a meaning – it eva luates to the `Type` object for the class `Foo` – so we can't use that to refer to the unnamed constructor.
132
133 We will introduce the notation `Foo.new`. This is currently a syntax error, so i t doesn't conflict with any existing code.
134
135 For named constructors, an expression like `Foo<int>.bar` (not followed by argum ents like the cases above) is not currently allowed by the syntax, so there is n o conflict.
136
137 This tear-off syntax is something we want in any case, independently of the opti onal new/const changes above. However, the syntax completely subsumes the option al `new` feature; with tear-off syntax, `Foo.bar(42)` is just the tear-off `Foo. bar` expression called as a function. You'd have to write `Foo.new(42)` instead of just `Foo(42)` (which is an argument for re-purposing the `Foo` expression to refer to the constructor instead of the type).
138 That is, if we have constructor tear-offs, the only feature of optional `new` th at isn't covered is calling the unnamed constructor.
139
140
141 ### Informal specification
142
143 * An expression *x* on one of the forms:
144 * `Foo.new`
145 * `Foo<types>.new`
146 * `Foo.bar`
147 * `Foo<types>.bar`
148 * `prefix.Foo.new`
149 * `prefix.Foo<types>.new`
150 * `prefix.Foo.bar`
151 * `prefix.Foo<types>.bar`
152
153 where `Foo` and `prefix.Foo` denotes a class and `bar` is a constructor of ` Foo`, and the expression is not followed by arguments `(args)`, is no longer an error.
154
155 Not included are expressions like `Foo..new(x)` or `Foo..bar(x)`. This is ac tually an argument against adding static cascades (`C..foo()..bar()` isn't curre ntly a static call, it's a cascade on the `Type` object).
156
157 * Instead of being an error, the expression evaluates to a function value
158 * with the same signature as the constructor (same parameters, default val ues, and having `Foo` or `Foo<types>` as return type),
159 * which, when called with `args`, returns the same result as `new x'(args) ` where `x'` is `x` without any `.new`.
160 * if `Foo` is not generic, the expression is a canonicalized compile-time constant (like a static method).
161 * If `Foo` is generic, the function is `==` to another tear off of the sam e constructor from "the same instantiation" of the class (like an instance metho d tear-off). We have to nail down what "the same instantiation" means, especiall y if `void == Object` in our type system.
162 * This feature be *implemented* by adding a static method for each non-generic class constructor:
163
164 ```dart
165 class C {
166 C(x1, …, xn) : … { body }
167 static C C_asFunction(x1, … , xn) => new C(x1, … , xn);
168 }
169 ```
170
171 The tear-off of `C.new` is just `C_asFunction`.
172
173 * … and adding a new helper class for each generic class with constructors:
174
175 ```dart
176 class D<T> {
177 D(x1, …, xn) : … { body }
178 }
179 class D_constructors<T> {
180 const D_constructors();
181 D_asFunction(x1, …, xn) => new D<T>(x1, …, xn);
182 }
183 ```
184
185 Then the tear-off of `D<T>.new` is `const D_constructors<T>().D_asFunction`. If the type `T` is a non-const type parameter, the equality is harder to preser ve, and the implementation might need to cache and canonicalize the `D_construct ors` instances that it does the method tear-offs from, or some other clever hack .
186
187 * In strong mode, method type parameters are not erased, so the implementation might be able to just create a closure containing the type parameter without a helper class (but equality might be harder to get correct that way).
188 * In most cases, implementations should be able to be more efficient than this rewriting if they can refer directly to their representation of the constructor .
189
190 ### Alternatives
191 Instead of introducing a new syntax, `Foo.new`, we could potentially re-purpose the plain `Foo` to refer to the constructor and introduce a new syntax for the ` Type` object for the class, say the Java-esque `Foo.class`. It would be a major breaking change, though, even if it could be mechanized. We should consider whet her it's feasible to make this change, because it gives much better uniformity i n what `Foo` means.
192
193 An argument against this approach would be that `Foo` means the type in most oth er cases, including `x is Foo`, and we plant to make the `is` oeprator work with type objects as well as literal types. It would be problematic if `x is Foo` is ambiguous, and we want to be backwards compatible.
194
195 ## Optional new/const in *potentially* const expressions
196
197 Together, the "optional const" and "optional new" features describe what happens if you omit the operator on a constructor call in a const or normal expression. However, there is one more kind of expression in Dart - the *potentially consta nt expression*, which only occurs in the initializer list of a generative const constructor.
198
199 Potentially constant expressions have the problem that you can't write `new Foo( x)` in them, because that expression is never constant, and you can't write `con st Foo(x)` if `x` is a parameter, because `x` isn't always constant. The same pr oblem applies to list and map literals.
200
201 Allowing you to omit the `new`/`const`, and just write nothing, gives us a way t o provide a new meaning to a constructor invocation (and list and map literals) in a potentially const expression: Treat it as `const` when invoked as a const c onstructor, and as `new` when invoking normally.
202
203 This also allows you to use the *type parameters* of the constructor to create n ew objects, like `class Foo<T> { final List<T> list; const Foo(int length) : lis t = List<T>(length); }`. Basically, it can treat the type parameter as a potenti ally constant variable as well, and use it.
204
205 The sub-expressions must still all be potentially const, but that's not a big pr oblem.
206
207 It does introduce another problem that is harder to handle - avoiding infinite r ecursion at compile-time.
208
209 If a constructor can call another constructor as a potentially constant expressi on, then it's possible to recurse deeply - or infinitely.
210
211 Example:
212
213
214 ```dart
215 class C {
216 final int value;
217 final C left;
218 final C right;
219 const C(int start, int depth)
220 : left = (depth == 0 ? null : C(start, depth - 1)),
221 value = start + (1 << depth),
222 right = (depth == 0 ? null : C(start + (1 << depth), depth - 1));
223 }
224 ```
225
226 This class would be able to generate a complete binary tree of any depth as a co mpile-time constant, using only *potentially constant* expressions and `const`/` new`-less constructor calls.
227
228 It's very hard to distinguish this case from one that recurses infinitely, and t he latter needs to be able to be caught and rejected at compile-time. We need to add some cycle-detection to the semantics to prevent arbitrary recursion. Since no recursion is currently possible, it won't break anything.
229
230 Proposed restriction: Don't allow a constant constructor invocation to invoke th e same constructor again *directly*, where "directly" means:
231
232 * as a sub-expression of an expression in the initializer list, or
233 * *directly* in the initializer list of another const constructor that is invo ked by a sub-expression in the initializer list.
234
235 This transitively prevents the unfolding of the constructor calls to recurse wit hout any limiting constraint.
236
237 It does not prevent the invocation from referring to a const variable whose valu e was created using the same constructor, so the following is allowed:
238
239
240 ```dart
241 const c0 = const C(0);
242 const c43 = const C(43);
243 class C {
244 final v;
245 const C(x) : v = ((x % 2 == 0) ? x : c0); // Silly but valid.
246 }
247 ```
248
249
250 The `const C(0)` invocation does not invoke `C` again, and the `const C(43)` inv ocation doesn't invoke `C` again, it just refers to another (already created) co nst value.
251
252 As usual, a const *variable* cannot refer to itself when its value is evaluated.
253
254 This restriction avoids infinite regress because the number of const variables a re at most linear in the source code of the program while still allowing some re ference to values of the same type.
255
256 Breaking the recursive constraint at variables also has the advantage that a con st variable can be represented only by its value. It doesn't need to remember wh ich constructors were used to create that value, just to be able to give an erro r in cases where that constructor refers back to the variable.
257
258 This feature is more invasive and complicated than the previous three. If this f eature is omitted, the previous three features still makes sense and should be i mplemented anyway.
259
260 ### Prior discussion
261
262 See: [issue 18241](http://dartbug.com/18241)
263
264 ### Informal specification
265
266 In short:
267
268 * A const constructor introduces a "potentially const context" for its initial izer list.
269 * This is treated similarly to a const context when the constructor is invoked in a const expression and as normal expression when the constructor is invoked as a non-const expression.,
270 * This means that `const` can be omitted in front of `List` literals, `Map` li terals and constructor invocations.
271 * All subexpressions of such expressions must still be *potentially const expr essions*, otherwise it's still an error.
272 * It is a compile-time error if a const constructor invoked in a const express ion causes itself to be invoked again *directly* (immediately in the initializer list or recursively while evaluating another const constructor invocation). It' s not a problem to refer to a const variable that is created using the same cons tructor. (This is different from what the VM currently does - the analyzer doesn 't detect cycles, and dart2js stack-overflows).
273 * The grammar allows `type<typeArguments>(args)` and `type<typeArguments>.foo( args)` as an expression in potentially const contexts, where the latter isn't cu rrently valid syntax, and the former wouldn't be allowed in a const constructor.
274 * This is not just syntactic sugar:
275 * It makes const and non-const constructor invocations differ in behavior. This alone can be simulated by treating it as two different constructors (perha ps even rewriting it into two constructors, and change invocations to pick the c orrect one based on context).
276 * The const version of the constructor now allows parameters, including ty pe parameters, to occur as arguments to constructor calls and as list/map member s. This is completely new.
277 * The language still satisfies that there is only one compile-time constan t value associated with each `const` expression, but some expression in const co nstructor initializer lists are no longer const expressions, they are just used as part of creating (potentially nested) const values for the const expressions. Effectively the recursive constructor calls need to be unfolded at each creatio n point, not just the first level. Each such unfolding is guaranteed to be finit e because it can't call the same constructor recursively and it stops at const v ariable references (or literals). It *can* have size exponential in the code siz e, though.
278
279
280
281 ## Migration
282
283 All the changes in this document are non-breaking - they assign meaning to synta x that was previously an error, either statically or dynamically. As such, code does not *need* to be migrated.
284
285 We will want to migrate library, documentation and example code so they can serv e as good examples. It's not as important as features that affect the actual API . The most visible change will likely be that some constructors can now be torn off as a const expression and used as a parameter default value.
286
287 All other uses will occur inside method bodies or initializer expressions.
288
289 Removing `new` is easy, and can be done by a simple RegExp replace.
290
291 Removing nested `const` probably needs manual attention ("nested" isn't a regula r property).
292
293 Using constructor tear-offs will likely be the most visible change, with cases l ike:
294
295
296 ```dart
297 map.putIfAbsent(42, HashSet<int>.new); // Rather than map.putIfAbsent(42, () => HashSet<int>()))
298 bars.map(Foo.fromBar)... // rather than bars.map((x) => Foo.fromBar(x))
299 ```
300
301 Once the features are implemented, this can be either done once and for all, or incrementally since each change is independent, but we should plan for it.
302
303 ## Related possible features
304
305 ### Type variables in static methods
306
307 When you invoke a static method, you use the class name as a name-space, e.g., ` Foo.bar()`.
308
309 If `Foo` is a generic class, you are not allowed to write `Foo<int>.bar()`. Howe ver, that notation is necessary for optional `new`/`const` anyway, so we might c onsider allowing it in general. The meaning is simple: the type parameters of a surrounding class will be in scope for static methods, and can be used both in t he signature and the body of the static functions.
310
311 If the type parameter is omitted, it defaults to dynamic/is inferred to somethin g, and it can be captured by the `Foo<int>.bar` tear-off.
312
313 This is in agreement with the language specification that generally treats `List <int>` as a class and the generic `List` class declaration as declaring a mappin g from type arguments to classes.
314
315 It makes constructors and static methods more symmetric.
316
317 It's not entirely without cost - a static method on a class with a bound can onl y be used if you can properly instantiate the type parameter with something sati sfying the bound. A class like
318
319
320 ```dart
321 class C<T extends C<T>> {
322 int compare(T other);
323 static int compareAny(dynamic o1, dynamic o2) => o1.compare(o2);
324 }
325 ```
326
327
328 would not be usable as `C.compareAny(v1, v2)` because `C` cannot be automaticall y instantiated to a valid bound. That is a regression compared to now, where any static method can be called on any class without concern for the type bound. Th is regression might be reason enough to drop this feature.
329
330 Also, if the class type parameters are visible in members, including getters and setters, it should mean that that *static fields* would have to exist for each instantiation, not just once. That's so incompatible with the current behavior, and most likely completely unexpected to users. This idea is unlikely to ever ha ppen.
331
332 ### Instantiated Type objects
333
334 The changes in this document allows `Foo<T>` to occur:
335
336 * Followed by arguments, `Foo<T>(args)`
337 * Followed by an identifier, `Foo<T>.bar` (and optionally arguments).
338 * Followed by `new`, `Foo<T>.new`.
339
340 but doesn't allow `Foo<T>` by itself, not even for the non-named constructor.
341
342 The syntax is available, and needs to be recognized in most settings anyway, so we could allow it as a type literal expression. That would allow the expression `List<int>` to evaluate to the *Type* object for the class *List<int>*. It's bee n a long time (refused) request: [issue 23221](http://dartbug.com/23221).
343
344 The syntax will also be useful for instantiated generic method tear-off like `va r intContinuation = future.then<int>;`
345
346 ### Generic constructors
347
348 We expect to allow generic constructors.
349 Currently constructors are not generic the same way other methods are. Instead t hey have access to the class' type parameters, but they can't have separate type parameters.
350
351 We plan to allow this for name constructors, so we can write:
352 ```dart
353 class Map<K, V> {
354
355 factory Map.fromIterable<S>(
356 Iterable<S> values, {K key(S value), K value(S value)}) {
357
358 }
359
360 }
361 ```
362 Having generic constructors shouldn't add more syntax with optional `new` becaus e it uses the same syntax as generic method invocation. If anything, it makes th ings more consistent.
363
364 ### Inferred Constant Expression
365
366 An expression like `Duration(seconds: 2)` can be prefixed by either `const` or ` new`. The optional `new` feature would make this create a new object for each ev aluation.
367 However, since all arguments are constant and the constructor is `const`, it cou ld implicitly become a `const` expression instead.
368
369 This has some consequences – if you actually need a new object each time (say a `new Object()` to use as a marker or sentinel), you would now *have to* write `n ew` to get that behavior. This suggests that if we introduce this feature at all , we should do so at the same time as optional `new`, it would be a breaking cha nge to later change `Object()` from `new` to `const`.
370
371 This feature also interacts with optional const. An expression like `Foo(Bar())` , where both `Foo` and `Bar` are `const` constructors, can be either `const` or `new` instantiated. It would probably default to `new`, but writing `const` befo re either `Foo` or `Bar` would make the other be inferred as constant as well. I t's not clear that this is predictable for users (you can omit either, but not b oth `const` prefix without changing the meaning).
372
373 ### Revisions
374
375 0.5 (2017-02-24) Initial version.
376
377 0.6 (2017-06-08) Added "Migration" section, minor tweaks.
378
379 0.7 (2017-06-19) Reordered features, added more related features.
380
381 0.8 (2017-06-20) Fix-ups and typos.
382
383 0.9 (2017-06-20) Add more complications for using `Foo` as the tear-off syntax.
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698