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

Side by Side Diff: docs/language/informal/generic-function-type-alias.md

Issue 2841483003: Added informal generic method syntax and generic function type specs. (Closed)
Patch Set: Added paragraph on tricky typedef type arg bounds Created 3 years, 5 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 | docs/language/informal/generic-method-syntax.md » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
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 names of positional
180 parameters to be
181 omitted. The rationale for this change is that a function type where a
182 parameter has a specified name and no type is very likely to be a
183 mistake. For instance, `int Function(int)` should not be the type of a
184 function that accepts an argument named "int" of type `dynamic`, it should
185 specify `int` as the parameter type and allow the name to be
186 unspecified. It is still possible to opt in and specify the parameter name,
187 which may be useful as documentation, e.g., if several arguments have the
188 same type.*
189
190 The modification of the rule for the nonterminal `type` causes parsing
191 ambiguities. The following disambiguation rule applies:
192 If the parser is at a location L where the tokens starting
193 at L may be a `type` or some other construct (e.g., in the body of a
194 method, when parsing something that may be a statement and may also be a
195 declaration), the parser must commit to parsing a `type` if it
196 is looking at the identifier `Function` followed by `<` or `(`, or it
197 is looking at a `type` followed by the identifier `Function` followed by `<`
198 or `(`.
199
200 *Note that this disambiguation rule does require parsers to have unlimited
201 lookahead. However, if a parsing strategy is used where the token
202 stream already contains references from each opening bracket (such as `<`
203 or `(`) to the corresponding closing bracket then the decision can be
204 taken in a fixed number of steps: If the current token is `Function` then
205 check the immediate successor (`<` or `(` means yes, we are looking at
206 a `type`, everything else means no) and we're done; if the first token is
207 an `identifier` other than `Function` then we can check whether it is a
208 `qualified` by looking at no more than the two next tokens, and we may then
209 check whether the next token again is `<`; if it is not then we look for
210 `Function` and the token after that, and if it is `<` then look for the
211 corresponding `>` (we have now skipped a generic class type), and then
212 the successor to that token again must be `Function`, and we finally check
213 its successor (looking for `<` or `(` again). This skips over the
214 presumed type arguments to a generic class type without checking that they
215 are actually type arguments, but we conjecture that there are no
216 syntactically correct alternatives (for example, we conjecture that there
217 is no syntactically correct statement, not a declaration, starting with
218 `SomeIdentifier<...> Function(...` where the angle brackets are balanced).*
219
220 *Note that this disambiguation rule will prevent parsing some otherwise
221 correct programs. For instance, the declaration of an asynchronous function
222 named `Function` with an omitted return type (meaning `dynamic`) and an
223 argument named `int` of type `dynamic` using `Function(int) async {}` will
224 be a parse error, because the parser will commit to parsing a type after
225 having seen "`Function(`" as a lookahead. However, we do not expect that it
226 will be a serious problem for developers to be unable to write such
227 programs.*
228
229 ## Scoping
230
231 Consider a typedef declaration as introduced by this feature, i.e., a
232 construct on the form
233
234 ```
235 metadata 'typedef' identifier typeParameters? '=' functionType ';'
236 ```
237
238 This declaration introduces `identifier` into the enclosing library scope.
239
240 Consider a parameterized typedef, i.e., a construct on the form
241
242 ```
243 metadata 'typedef' identifier typeParameters '=' functionType ';'
244 ```
245
246 Note that in this case the `typeParameters` cannot be omitted. This
247 construct introduces a scope known as the *typedef scope*. Each typedef
248 scope is nested inside the library scope of the enclosing library. Every
249 formal type parameter declared by the `typeParameters` in this construct
250 introduces a type variable into its enclosing typedef scope. The typedef
251 scope is the current scope for the `typeParameters` themselves, and for the
252 `functionType`.
253
254 Consider a `functionType` specifying a generic function type, i.e., a
255 construct on the form
256
257 ```
258 returnType? 'Function' typeParameters parameterTypeList
259 ```
260
261 Note again that `typeParameters` are present, not optional. This construct
262 introduces a scope known as a *function type scope*. The function type
263 scope is nested inside the current scope for the associated `functionType`.
264 Every formal type parameter declared by the `typeParameters` introduces a
265 type variable into its enclosing function type scope. The function type
266 scope is the current scope for the entire `functionType`.
267
268 *This implies that parameterized typedefs and function types are capable of
269 specifying F-bounded type parameters, because the type parameters are in
270 scope in the type parameter list itself.*
271
272 ## Static Analysis
273
274 Consider a typedef declaration as introduced by this feature, i.e., a
275 construct on the form
276
277 ```
278 metadata 'typedef' identifier typeParameters? '=' functionType ';'
279 ```
280
281 It is a compile-time error if a name *N* introduced into a library scope by
282 a typedef has an associated `functionType` which depends directly or
283 indirectly on *N*. It is a compile-time error if a bound on a formal type
284 parameter in `typeParameters` is not a type. It is a compile-time error if
285 a typedef has an associated `functionType` which is not a well-bounded type
286 when analyzed under the assumption that every identifier resolving to a
287 formal type parameter in `typeParameters` is a type satisfying its bound. It
288 is a compile-time error if an instantiation *F<T1..Tk>* of a parameterized
289 typedef is mal-bounded.
290
291 *This implies that a typedef cannot be recursive. It can only introduce a
292 name as an alias for a type which is already expressible as a
293 `functionType`, or a name for a type-level function F where every
294 well-bounded invocation `F<T1..Tk>` denotes a type which could be expressed
295 as a `functionType`. In the terminology of
296 [kind systems](https://en.wikipedia.org/wiki/Kind_(type_theory)), we
297 could say that a typedef can define entities of kind ` * ` and of kind
298 ` * -> * `, and, when it is assumed that every formal type parameter of the
299 typedef (if any) has kind ` * `, it is an error if the right hand side of the
300 declaration denotes an entity of any other kind than ` * `; in particular,
301 declarations of entities of kind ` * -> * ` cannot be curried.*
302
303 *Note that the constraints required to ensure that the body of a `typedef`
304 is well-bounded may not be expressible in the language with some otherwise
305 reasonable declarations:
306 ``` dart
307 typedef F<X> = void Function(X);
308 class C<Y extends F<num>> {}
309 typedef G<Z> = C<F<Z>> Function();
310 ```
311 The formal type parameter `Z` must be a supertype of `num` in order to
312 ensure that `F<Z>` is a subtype of the bound `F<num>`, but we do not support
313 lower bounds on type arguments in Dart. Consequently, a declaration like
314 `G` is a compile-time error no matter which bound we specify for `Z`, because
315 no bound will ensure that the body is well-bounded for all possible `Z`.
316 Similarly, the body of a `typedef` may use a given type argument in
317 two or more different covariant contexts, which may require a bound which
318 is a subtype of the constraints needed for each of those usages; for
319 nominal types we would need an intersection type constructor in order to
320 express a useful constraint in this situation. A richer type algebra
321 may be added to Dart in the future which could allow more of these
322 complex `typedef`s, but it is not obvious that it is useful enough to
323 justify the added complexity.*
324
325 It is a compile-time error if a name declared in a typedef, with or without
326 actual type arguments, is used as a superclass, superinterface, or mixin. It
327 is a compile-time error if a generic function type is used as a bound for a
328 formal type parameter of a class or a function. It is a compile-time error if
329 a generic function type is used as an actual type argument.
330
331 *Generic function types can thus only be used in the following situations:*
332
333 - *as a type annotation on an local, instance, static, or global variable.*
334 - *as a function return or parameter type.*
335 - *in a type test.*
336 - *in a type cast.*
337 - *in an on-catch clause.*
338 - *as a parameter or return type in a function type.*
339
340 *The motivation for having this constraint is that it ensures that the Dart type
341 system admits only predicative types. It does admit non-prenex types, e.g.,
342 `int Function(T function<T>(T) f)`. From research into functional calculi
343 it is well-known that impredicative types give rise to undecidable subtyping,
344 e.g.,
345 [(Pierce, 1993)](http://www2.tcs.ifi.lmu.de/lehre/SS07/Typen/pierce93bounded.pdf ),
346 and even though the Dart type system is very different from F-sub, we cannot
347 assume that these difficulties are absent.*
348
349 ## Dynamic Semantics
350
351 The addition of this feature does not change the dynamic semantics of
352 Dart.
353
354 ## Changes
355
356 2017-May-31: Added constraint on usage of generic function types: They
357 cannot be used as type parameter bounds nor as type arguments.
358
359 2017-Jan-04: Adjusted the grammar to require named parameter types to have
360 a type (previously, the type was optional).
361
362 2016-Dec-21: Changed the grammar to prevent the new function type syntax
363 in several locations (for instance, as a super class or as a mixin). The
364 main change in the grammar is the introduction of `typeWithoutFunction`.
365
366 2016-Dec-15: Changed the grammar to prevent the old style function types
367 (derived from `functionSignature` in the grammar) from occurring inside
368 the new style (`functionType`).
OLDNEW
« no previous file with comments | « no previous file | docs/language/informal/generic-method-syntax.md » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698