OLD | NEW |
---|---|
(Empty) | |
1 # Feature: Generic Function Type Alias | |
2 | |
3 **Status**: Under implementation. | |
4 | |
5 **This document** is an informal specification of a feature supporting the | |
6 definition of function type aliases using a more expressive syntax than the | |
7 one available today, such that it also covers generic function types. The | |
8 feature also introduces syntax for specifying function types directly, such | |
9 that they can be used in type annotations etc. without going via a | |
10 `typedef`. This feature is being introduced into Dart starting Q4, 2016. | |
11 | |
12 **This feature** introduces a new syntactic form of typedef declaration | |
13 which includes an identifier and a type, connecting the two with an equals | |
14 sign, `=`. The effect of such a declaration is that the name is declared to | |
15 be an alias for the type. Type parameterization may occur in the | |
16 declaration itself, as well as in the declared type. This feature also | |
17 introduces syntax for specifying function types directly, using a syntax | |
18 which is similar to the header of a function declaration. | |
19 | |
20 The **motivation** for adding this feature is that it allows developers to | |
21 specify generic function types everywhere a type is expected, including | |
22 type annotations, return types, actual type arguments, and formal type | |
23 parameter bounds. Currently there is no way to specify a generic function | |
24 type in these situations. Even in the case where a generic function type | |
25 *can* be specified (such as a type annotation for a formal parameter) it | |
26 may be useful for readability to declare a name as an alias of a complex | |
27 type, and use that name instead of the type. | |
28 | |
29 ## Examples | |
30 | |
31 Using the new syntax, a function type alias may be declared as follows: | |
32 | |
33 ```dart | |
34 typedef F = List<T> Function<T>(T); | |
35 ``` | |
36 | |
37 This declares `F` to be the type of a function that accepts one type | |
38 parameter `T` and one value parameter of type `T` whose name is | |
39 unspecified, and returns a result of type `List<T>`. It is possible to use | |
40 the new syntax to declare function types that we can already declare using | |
41 the existing typedef declaration. For instance, `G` and `H` both declare | |
42 the same type: | |
43 | |
44 ```dart | |
45 typedef G = List<int> Function(int); // New form. | |
46 typedef List<int> H(int i); // Old form. | |
47 ``` | |
48 | |
49 Note that the name of the parameter is required in the old form, but the | |
50 type may be omitted. In contrast, the type is required in the new form, but | |
51 the name may be omitted. | |
52 | |
53 The reason for having two ways to express the same thing is that the new | |
54 form seamlessly covers non-generic functions as well as generic ones, and | |
55 developers might prefer to use the new form everywhere, for improved | |
56 readability. | |
57 | |
58 *We may deprecate the old form after a while, or we may choose | |
59 to keep it, because it is more concise. We may even change the old form to | |
60 allow omitting the name and not the type when only one identifier is | |
61 specified, if this is not too much of a breaking change. As an intermediate | |
62 step we could change the old form to always require both the type and the | |
63 name, such that no type expressions will silently change meaning.* | |
64 | |
65 There is a difference between declaring a generic function type and | |
66 declaring a typedef which takes a type argument. The former is a | |
67 declaration of a single type which describes a certain class of runtime | |
68 entities: Functions that are capable of accepting some type arguments as | |
69 well as some value arguments, both at runtime. The latter is a type-level | |
70 function: It accepts a type argument at compile time and returns a type, | |
71 which may be used, say, as a type annotation. Dart has had support for | |
72 parameterized typedefs for a while, and the new syntax supports | |
73 parameterized typedefs as well. Here is an example of a parameterized | |
74 typedef, and a usage thereof: | |
75 | |
76 ```dart | |
77 typedef I<T> = List<T> Function(T); // New form. | |
78 typedef List<T> J<T>(T t); // Old form. | |
79 I<int> myFunction(J<int> f) => f; | |
80 ``` | |
81 | |
82 Here, we have declared two equivalent parameterized typedefs `I` and `J`, | |
83 and we have used an instantiation of each of them in the type annotations | |
84 on `myFunction`. Note that the type of `myFunction` does not include *any* | |
85 generic types, it is just a function that accepts an argument and returns a | |
86 result, both of which have a non-generic function type that we have | |
87 obtained by instantiating a parameterized typedef. The argument type might | |
88 as well have been declared using the traditional function signature syntax, | |
89 and the return type (and the argument type, by the way) might as well have | |
90 been declared using a regular, non-parameterized typedef: | |
91 | |
92 ```dart | |
93 typedef List<int> K(int i); // Old form, non-generic. | |
94 K myFunction2(List<int> f(int i)) => f; // Same as myFunction. | |
95 ``` | |
96 | |
97 The new syntax allows for using the two kinds of type parameters together: | |
98 | |
99 ```dart | |
100 typedef L<T> = List<T> Function<S>(S, {T Function(int, S) factory}); | |
101 ``` | |
102 | |
103 This declares `L` to be a parameterized typedef; when instantiating `L` | |
104 with an actual type argument as in `L<String>`, it becomes the type of a | |
105 generic function that accepts a type argument `S` and two value arguments: | |
106 one required positional argument of type `S`, and one named optional | |
107 argument with name `factory` and type `String Function(int, S)`; finally, | |
108 it returns a value of type `List<String>`. | |
109 | |
110 ## Syntax | |
111 | |
112 The new form of `typedef` declaration uses the following syntax (there are | |
113 no deletions from the grammar; addition of a new rule or a new alternative | |
114 in a rule is marked with NEW and modified rules are marked CHANGED): | |
115 | |
116 ``` | |
117 typeAlias: | |
118 metadata 'typedef' typeAliasBody | | |
119 metadata 'typedef' identifier typeParameters? '=' functionType ';' // NEW | |
120 functionType: // NEW | |
121 returnType? 'Function' typeParameters? parameterTypeList | |
122 parameterTypeList: // NEW | |
123 '(' ')' | | |
124 '(' normalParameterTypes ','? ')' | | |
125 '(' normalParameterTypes ',' optionalParameterTypes ')' | | |
126 '(' optionalParameterTypes ')' | |
127 normalParameterTypes: // NEW | |
128 normalParameterType (',' normalParameterType)* | |
129 normalParameterType: // NEW | |
130 type | typedIdentifier | |
131 optionalParameterTypes: // NEW | |
132 optionalPositionalParameterTypes | namedParameterTypes | |
133 optionalPositionalParameterTypes: // NEW | |
134 '[' normalParameterTypes ','? ']' | |
135 namedParameterTypes: // NEW | |
136 '{' typedIdentifier (',' typedIdentifier)* ','? '}' | |
137 typedIdentifier: // NEW | |
138 type identifier | |
139 type: // CHANGED | |
140 typeWithoutFunction | | |
141 functionType | |
142 typeWithoutFunction: // NEW | |
143 typeName typeArguments? | |
144 typeWithoutFunctionList: // NEW | |
145 typeWithoutFunction (',' typeWithoutFunction)* | |
146 mixins: // CHANGED | |
147 'with' typeWithoutFunctionList | |
148 interfaces: // CHANGED | |
149 'implements' typeWithoutFunctionList | |
150 superclass: // CHANGED | |
151 'extends' typeWithoutFunction | |
152 mixinApplication: // CHANGED | |
153 typeWithoutFunction mixins interfaces? | |
154 newExpression: // CHANGED | |
155 'new' typeWithoutFunction ('.' identifier)? arguments | |
156 constObjectExpression: // CHANGED | |
157 'const' typeWithoutFunction ('.' identifier)? arguments | |
158 redirectingFactoryConstructorSignature: // CHANGED | |
159 'const'? 'factory' identifier ('.' identifier)? | |
160 formalParameterList '=' typeWithoutFunction ('.' identifier)? | |
161 ``` | |
162 | |
163 The syntax relies on treating `Function` as a fixed element in a function | |
164 type, similar to a keyword or a symbol (many languages use symbols like | |
165 `->` to mark function types). | |
166 | |
167 *The rationale for using this form is that it makes a function type very | |
168 similar to the header in a declaration of a function with that type: Just | |
169 replace `Function` by the name of the function, and add missing parameter | |
170 names and default values.* | |
171 | |
172 *The syntax differs from the existing function type syntax | |
173 (`functionSignature`) in that the existing syntax allows the type of a | |
174 parameter to be omitted, but the new syntax allows parameter names to be | |
175 omitted. The rationale for this change is that a function type where a | |
Lasse Reichstein Nielsen
2017/06/01 07:56:59
... allows names of positional parameters to be om
eernst
2017/07/12 13:15:37
Done.
| |
176 parameter has a specified name and no type is very likely to be a | |
177 mistake. For instance, `int Function(int)` should not be the type of a | |
178 function that accepts an argument named "int" of type `dynamic`, it should | |
179 specify `int` as the parameter type and allow the name to be | |
180 unspecified. It is still possible to opt in and specify the parameter name, | |
181 which may be useful as documentation, e.g., if several arguments have the | |
182 same type.* | |
183 | |
184 The modification of the rule for the nonterminal `type` may cause parsing | |
Lasse Reichstein Nielsen
2017/06/01 07:56:59
May? Does it or does it not?
(I don't know, but we
eernst
2017/07/12 13:15:37
Done.
| |
185 ambiguities. We intend to handle them by the following disambiguation rule | |
186 in the parser: If the parser is at a location L where the tokens starting | |
187 at L may be a `type` or some other construct (e.g., in the body of a | |
188 method, when parsing something that may be a statement and may also be a | |
189 declaration), the parser can commit to parsing a type by detecting that it | |
190 is looking at the identifier `Function` followed by `<` or `(`, or that it | |
191 is looking at a type followed by the identifier `Function` followed by `<` | |
192 or `(`. | |
193 | |
194 *Note that this disambiguation rule does require parsers to have unlimited | |
195 lookahead. However, if a "diet parsing" strategy is used where the token | |
Lasse Reichstein Nielsen
2017/06/01 07:56:59
Why unlimited? Isn't 2 tokens look-ahead enough?
eernst
2017/07/12 13:15:37
Done. The lookahead has no finite limit: In `foo(a
| |
196 stream already contains references from each opening bracket (such as `<` | |
197 or `(`) to the corresponding closing bracket then the decision can be | |
198 taken in a fixed number of steps: If the current token is `Function` then | |
199 check the immediate successor (`<` or `(` means yes, we are looking at | |
200 a `type`, everything else means no) and we're done; if the first token is | |
201 an `identifier` other than `Function` then we can check whether it is a | |
202 `qualified` by looking at no more than the two next tokens, and we may then | |
203 check whether the next token again is `<`; if it is not then we look for | |
204 `Function` and the token after that, and if it is `<` then look for the | |
205 corresponding `>` (we have now skipped a generic class type), and then | |
206 the successor to that token again must be `Function`, and we finally check | |
207 its successor (looking for `<` or `(` again). This skips over the | |
208 presumed type arguments to a generic class type without checking that they | |
209 are actually type arguments, but we conjecture that there are no | |
210 syntactically correct alternatives (for example, we conjecture that there | |
211 is no syntactically correct statement, not a declaration, starting with | |
212 `SomeIdentifier<...> Function(...` where the angle brackets are balanced).* | |
213 | |
214 *Note that this disambiguation rule will prevent parsing some otherwise | |
215 correct programs. For instance, the declaration of an asynchronous function | |
216 named `Function` with an omitted return type (meaning `dynamic`) and an | |
217 argument named `int` of type `dynamic` using `Function(int) async {}` will | |
218 be a parse error, because the parser will commit to parsing a type after | |
219 having seen "`Function(`" as a lookahead. However, we do not expect that it | |
220 will be a serious problem for developers to be unable to write such | |
221 programs.* | |
222 | |
223 ## Scoping | |
224 | |
225 Consider a typedef declaration as introduced by this feature, i.e., a | |
226 construct on the form | |
227 | |
228 ``` | |
229 metadata 'typedef' identifier typeParameters? '=' functionType ';' | |
230 ``` | |
231 | |
232 This declaration introduces `identifier` into the enclosing library scope. | |
233 | |
234 Consider a parameterized typedef, i.e., a construct on the form | |
235 | |
236 ``` | |
237 metadata 'typedef' identifier typeParameters '=' functionType ';' | |
238 ``` | |
239 | |
240 Note that in this case the `typeParameters` are present, not optional. This | |
241 construct introduces a scope known as the *typedef scope*. Each typedef | |
242 scope is nested inside the library scope of the enclosing library. Every | |
243 formal type parameter declared by the `typeParameters` in this construct | |
244 introduces a type variable into its enclosing typedef scope. The typedef | |
245 scope is the current scope for the `typeParameters` themselves, and for the | |
246 `functionType`. | |
247 | |
248 Consider a `functionType` specifying a generic function type, i.e., a | |
249 construct on the form | |
250 | |
251 ``` | |
252 returnType? 'Function' typeParameters parameterTypeList | |
253 ``` | |
254 | |
255 Note again that `typeParameters` are present, not optional. This construct | |
256 introduces a scope known as a *function type scope*. The function type | |
257 scope is nested inside the current scope for the associated `functionType`. | |
258 Every formal type parameter declared by the `typeParameters` introduces a | |
259 type variable into its enclosing function type scope. The function type | |
260 scope is the current scope for the entire `functionType`. | |
261 | |
262 *This implies that parameterized typedefs and function types are capable of | |
263 specifying F-bounded type parameters, because the type parameters are in | |
264 scope in the type parameter list itself.* | |
265 | |
266 ## Static Analysis | |
267 | |
268 Consider a typedef declaration as introduced by this feature, i.e., a | |
269 construct on the form | |
270 | |
271 ``` | |
272 metadata 'typedef' identifier typeParameters? '=' functionType ';' | |
273 ``` | |
274 | |
275 It is a compile-time error if a name *N* introduced into a library scope by | |
276 a typedef has an associated `functionType` which depends directly or | |
277 indirectly on *N*. It is a compile-time error if a bound on a formal type | |
278 parameter in `typeParameters` is not a type. It is a compile-time error if | |
279 a typedef has an associated `functionType` which is not a type when | |
280 analyzed under the assumption that every identifier resolving to a formal | |
281 type parameter in `typeParameters` is a type. It is a compile-time error if | |
282 an instantiation *F<T1..Tk>* of a parameterized typedef is mal-bounded. | |
283 | |
284 *This implies that a typedef cannot be recursive. It can only introduce a | |
285 name as an alias for a type which is already expressible as a | |
286 `functionType`, or a name for a type-level function F where every | |
287 well-bounded invocation `F<T1..Tk>` denotes a type which could be expressed | |
288 as a `functionType`. Following | |
289 [common terminology](https://en.wikipedia.org/wiki/Kind_(type_theory)), we | |
290 could say that a typedef can define entities of kind ` * ` and of kind | |
291 ` * -> * `, and, when it is assumed that every formal type parameter of the | |
292 typedef (if any) has kind ` * `, it is an error if the right hand side of the | |
293 declaration denotes an entity of any other kind than ` * `; in particular, | |
294 declarations of entities of kind ` * -> * ` cannot be curried.* | |
295 | |
296 It is a compile-time error if a name declared in a typedef, with or without | |
297 actual type arguments, is used as a superclass, superinterface, or mixin. | |
298 | |
299 ## Dynamic Semantics | |
300 | |
301 The addition of this feature does not change the dynamic semantics of | |
302 Dart. | |
303 | |
304 ## Changes | |
305 | |
306 2017-Jan-04: Adjusted the grammar to require named parameter types to have | |
307 a type (previously, the type was optional). | |
308 | |
309 2016-Dec-21: Changed the grammar to prevent the new function type syntax | |
310 in several locations (for instance, as a super class or as a mixin). The | |
311 main change in the grammar is the introduction of `typeWithoutFunction`. | |
312 | |
313 2016-Dec-15: Changed the grammar to prevent the old style function types | |
314 (derived from `functionSignature` in the grammar) from occurring inside | |
315 the new style (`functionType`). | |
OLD | NEW |