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