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 |
| 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`). |
OLD | NEW |