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

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: Address comments 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.7 (2017-06-19)
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, it's just the plain list and map literal syntax.
floitsch 2017/06/19 13:33:48 for new syntax, since that syntax is already used
Lasse Reichstein Nielsen 2017/06/20 11:18:43 Done.
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 `Nyclass<SomeType>.name(arg)`.
floitsch 2017/06/19 13:33:48 Myclass ?
Lasse Reichstein Nielsen 2017/06/20 11:18:43 Done.
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 could potentially change that, re-purpose the plain `Foo` to refer to the con structor 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 whether it's feasible to make this chan ge.
floitsch 2017/06/19 13:33:48 I would move this into a separate section. "Altern
Lasse Reichstein Nielsen 2017/06/20 11:18:43 Done.
134
135 Otherwise we will introduce the notation `Foo.new`. This is currently a syntax e rror, so it doesn't conflict with any existing code.
136
137 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.
138
139 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 `ne` feature; with tear-off syntax, `Foo.bar(42)` is just the tear-off `Foo.b ar` expression called as a function. You'd have to write `Foo.new(42)` instead o f just `Foo(42)` (which is an argument for re-purposing the `Foo` expression to refer to the constructor instead of the type).
floitsch 2017/06/19 13:33:48 optional `new` feature. (missing "w")
Lasse Reichstein Nielsen 2017/06/20 11:18:43 Done.
140 That is, if we have constructor tear-offs, the only feature of optional `new` th at isn't covered is calling the unnamed constructor.
141
142
143 ### Informal specification
144
145 * An expression *x* on one of the forms:
146 * `Foo.new`
147 * `Foo<types>.new`
148 * `Foo.bar`
149 * `Foo<types>.bar`
150 * `prefix.Foo.new`
151 * `prefix.Foo<types>.new`
152 * `prefix.Foo.bar`
153 * `prefix.Foo<types>.bar`
154
155 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.
156
157 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).
158
159 * Instead of being an error, the expression evaluates to a function value
160 * with the same signature as the constructor (same parameters, default val ues, and having `Foo` or `Foo<types>` as return type),
161 * which, when called with `args`, returns the same result as `new x'(args) ` where `x'` is `x` without any `.new`.
162 * if `Foo` is not generic, the expression is a canonicalized compile-time constant (like a static method).
163 * 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.
164 * This feature be *implemented* by adding a static method for each non-generic class constructor:
165
166 ```dart
167 class C {
168 C(x1, …, xn) : … { body }
169 static C C_asFunction(x1, … , xn) => new C(x1, … , xn);
170 }
171 ```
172
173 The tear-off of `C.new` is just `C_asFunction`.
174
175 * … and adding a new helper class for each generic class with constructors:
176
177 ```dart
178 class D<T> {
179 D(x1, …, xn) : … { body }
180 }
181 class D_constructors<T> {
182 const D_constructors();
183 D_asFunction(x1, …, xn) => new D<T>(x1, …, xn);
184 }
185 ```
186
187 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 .
188
189 * 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).
190 * 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 .
191
192
193 ## Optional new/const in *potentially* const expressions
194
195 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.
196
197 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.
198
199 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.
200
201 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.
202
203 The sub-expressions must still all be potentially const, but that's not a big pr oblem.
204
205 It does introduce another problem that is harder to handle - avoiding infinite r ecursion at compile-time.
206
207 If a constructor can call another constructor as a potentially constant expressi on, then it's possible to recurse deeply - or infinitely.
208
209 Example:
210
211
212 ```dart
213 class C {
214 final int value;
215 final C left;
216 final C right;
217 const C(int start, int depth)
218 : left = (depth == 0 ? null : C(start, depth - 1)),
219 value = start + (1 << depth),
220 right = (depth == 0 ? null : C(start + (1 << depth), depth - 1));
221 }
222 ```
223
224 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.
225
226 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.
227
228 Proposed restriction: Don't allow a constant constructor invocation to invoke th e same constructor again *directly*, where "directly" means:
229
230 * as a sub-expression of an expression in the initializer list, or
231 * *directly* in the initializer list of another const constructor that is invo ked by a sub-expression in the initializer list.
232
233 This transitively prevents the unfolding of the constructor calls to recurse wit hout any limiting constraint.
234
235 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:
236
237
238 ```dart
239 const c0 = const C(0);
240 const c43 = const C(43);
241 class C {
242 final v;
243 const C(x) : v = ((x % 2 == 0) ? x : c0); // Silly but valid.
244 }
245 ```
246
247
248 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.
249
250 As usual, a const *variable* cannot refer to itself when its value is evaluated.
251
252 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.
253
254 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.
255
256 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.
257
258 ### Prior discussion
259
260 See: [issue 18241](http://dartbug.com/18241)
261
262 ### Informal specification
263
264 In short:
265
266 * A const constructor introduces a "potentially const context" for its initial izer list.
267 * 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.,
268 * This means that `const` can be omitted in front of `List` literals, `Map` li terals and constructor invocations.
269 * All subexpressions of such expressions must still be *potentially const expr essions*, otherwise it's still an error.
270 * 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).
271 * 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.
272 * This is not just syntactic sugar:
273 * 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).
274 * 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.
275 * 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.
276
277
278
279 ## Migration
280
281 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.
282
283 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.
284
285 All other uses will occur inside method bodies or initializer expressions.
286
287 Removing `new` is easy, and can be done by a simple RegExp replace.
288
289 Removing nested `const` probably needs manual attention ("nested" isn't a regula r property).
290
291 Using constructor tear-offs will likely be the most visible change, with cases l ike:
292
293
294 ```dart
295 map.putIfAbsent(42, List<int>.new); // Rather than map.putIfAbsent(42, () => <i nt>[])
floitsch 2017/06/19 13:33:48 map.putIfAbsent(42, HashSet<int>.new); // Rather
Lasse Reichstein Nielsen 2017/06/20 11:18:43 Done. Also removed the `new` from the "rather than
296 bars.map(Foo.fromBar)... // rather than bars.map((x) => new Foo.fromBar(x))
297 ```
298
299 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.
300
301 ## Related possible features
302
303 ### Type variables in static methods
304
305 When you invoke a static method, you use the class name as a name-space, e.g., ` Foo.bar()`.
306
307 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.
308
309 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.
310
311 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.
312
313 It makes constructors and static methods more symmetric.
314
315 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
316
317
318 ```dart
319 class C<T extends C<T>> {
320 int compare(T other);
321 static int compareAny(dynamic o1, dynamic o2) => o1.compare(o2);
322 }
323 ```
324
325
326 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.
327
328 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.
329
330 ### Instantiated Type objects
331
332 The changes in this document allows `Foo<T>` to occur:
333
334 * Followed by arguments, `Foo<T>(args)`
335 * Followed by an identifier, `Foo<T>.bar` (and optionally arguments).
336 * Followed by `new`, `Foo<T>.new`.
337
338 but doesn't allow `Foo<T>` by itself, not even for the non-named constructor.
339
340 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).
341
342 The syntax will also be useful for instantiated generic method tear-off like `va r intContinuation = future.then<int>;`
343
344 ### Generic constructors
345
346 We expect to allow generic constructors.
347 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.
348
349 We plan to allow this for name constructors, so we can write:
350 ```dart
351 class Map<K, V> {
352
353 factory Map.fromIterable<S>(
354 Iterable<S> values, {K key(S value), K value(S value)}) {
355
356 }
357
358 }
359 ```
360 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.
361
362 ### Inferred Constant Expression
363
364 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.
365 However, since all arguments are constant and the constructor is `const`, it cou ld implicitly become a `const` expression instead.
366
367 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`.
368
369 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).
370
371 ### Revisions
372
373 0.5 (2017-02-24) Initial version.
374
375 0.6 (2017-06-08) Added "Migration" section, minor tweaks.
376
377 0.7 (2017-06-19) Reordered features, added more related features.
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