Chromium Code Reviews| 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 | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:52
Consider using "generic-function types" or "types
eernst
2017/07/11 15:50:04
Added a paragraph making the distinction between t
| |
| 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 **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 | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
I'm not sure I understand that. I think it's being
eernst
2017/07/11 15:50:04
Exactly.
| |
| 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 at all, and to specify function types | |
| 22 everywhere a type is expected. That includes type annotations, return types, | |
| 23 actual type arguments, and formal type parameter bounds. Currently there is | |
| 24 no way to specify a function type directly in these situations. Even in the | |
| 25 case where a function type *can* be specified (such as a type annotation for | |
| 26 a formal parameter) it may be useful for readability to declare a name as an | |
| 27 alias of a complex 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.* | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
This is all speculative, not descriptive, so it do
eernst
2017/07/11 15:50:04
Deleted it.
| |
| 64 | |
| 65 There is a difference between declaring a generic function type and | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
here maybe say "between declaring the type of a ge
eernst
2017/07/11 15:50:04
With the changes earlier in this document, the phr
| |
| 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, | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
"type-level function" uses "function" in a too-ove
eernst
2017/07/11 15:50:05
But it _is_ a type-level function, and a bunch of
Lasse Reichstein Nielsen
2017/07/12 12:08:06
Let's not write something that can only be underst
| |
| 71 which may be used, say, as a type annotation. We use the phrase | |
| 72 *parameterized typedef* to refer to the latter. Dart has had support for | |
| 73 parameterized typedefs for a while, and the new syntax supports | |
| 74 parameterized typedefs as well. Here is an example of a parameterized | |
| 75 typedef, and a usage thereof: | |
| 76 | |
| 77 ```dart | |
| 78 typedef I<T> = List<T> Function(T); // New form. | |
| 79 typedef List<T> J<T>(T t); // Old form. | |
| 80 I<int> myFunction(J<int> f) => f; | |
| 81 ``` | |
| 82 | |
| 83 Here, we have declared two equivalent parameterized typedefs `I` and `J`, | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
Here -> In this example
(just for explicitness)
eernst
2017/07/11 15:50:05
Done.
| |
| 84 and we have used an instantiation of each of them in the type annotations | |
| 85 on `myFunction`. Note that the type of `myFunction` does not include *any* | |
| 86 generic types, it is just a function that accepts an argument and returns a | |
| 87 result, both of which have a non-generic function type that we have | |
| 88 obtained by instantiating a parameterized typedef. The argument type might | |
| 89 as well have been declared using the traditional function signature syntax, | |
| 90 and the return type (and the argument type, by the way) might as well have | |
| 91 been declared using a regular, non-parameterized typedef: | |
| 92 | |
| 93 ```dart | |
| 94 typedef List<int> K(int i); // Old form, non-generic. | |
| 95 K myFunction2(List<int> f(int i)) => f; // Same as myFunction. | |
| 96 ``` | |
| 97 | |
| 98 The new syntax allows for using the two kinds of type parameters together: | |
| 99 | |
| 100 ```dart | |
| 101 typedef L<T> = List<T> Function<S>(S, {T Function(int, S) factory}); | |
| 102 ``` | |
| 103 | |
| 104 This declares `L` to be a parameterized typedef; when instantiating `L` | |
| 105 with an actual type argument as in `L<String>`, it becomes the type of a | |
| 106 generic function that accepts a type argument `S` and two value arguments: | |
| 107 one required positional argument of type `S`, and one named optional | |
| 108 argument with name `factory` and type `String Function(int, S)`; finally, | |
| 109 it returns a value of type `List<String>`. | |
| 110 | |
| 111 ## Syntax | |
| 112 | |
| 113 The new form of `typedef` declaration uses the following syntax (there are | |
| 114 no deletions from the grammar; addition of a new rule or a new alternative | |
| 115 in a rule is marked with NEW and modified rules are marked CHANGED): | |
| 116 | |
| 117 ``` | |
| 118 typeAlias: | |
| 119 metadata 'typedef' typeAliasBody | | |
| 120 metadata 'typedef' identifier typeParameters? '=' functionType ';' // NEW | |
| 121 functionType: // NEW | |
| 122 returnType? 'Function' typeParameters? parameterTypeList | |
| 123 parameterTypeList: // NEW | |
| 124 '(' ')' | | |
| 125 '(' normalParameterTypes ','? ')' | | |
| 126 '(' normalParameterTypes ',' optionalParameterTypes ')' | | |
| 127 '(' optionalParameterTypes ')' | |
| 128 normalParameterTypes: // NEW | |
| 129 normalParameterType (',' normalParameterType)* | |
| 130 normalParameterType: // NEW | |
| 131 type | typedIdentifier | |
| 132 optionalParameterTypes: // NEW | |
| 133 optionalPositionalParameterTypes | namedParameterTypes | |
| 134 optionalPositionalParameterTypes: // NEW | |
| 135 '[' normalParameterTypes ','? ']' | |
| 136 namedParameterTypes: // NEW | |
| 137 '{' typedIdentifier (',' typedIdentifier)* ','? '}' | |
| 138 typedIdentifier: // NEW | |
| 139 type identifier | |
| 140 type: // CHANGED | |
| 141 typeWithoutFunction | | |
| 142 functionType | |
| 143 typeWithoutFunction: // NEW | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:52
A positive description is usually easier to work w
eernst
2017/07/11 15:50:05
Right, it is "any type which is not syntactically
| |
| 144 typeName typeArguments? | |
| 145 typeWithoutFunctionList: // NEW | |
| 146 typeWithoutFunction (',' typeWithoutFunction)* | |
| 147 mixins: // CHANGED | |
| 148 'with' typeWithoutFunctionList | |
| 149 interfaces: // CHANGED | |
| 150 'implements' typeWithoutFunctionList | |
| 151 superclass: // CHANGED | |
| 152 'extends' typeWithoutFunction | |
| 153 mixinApplication: // CHANGED | |
| 154 typeWithoutFunction mixins interfaces? | |
| 155 newExpression: // CHANGED | |
| 156 'new' typeWithoutFunction ('.' identifier)? arguments | |
| 157 constObjectExpression: // CHANGED | |
| 158 'const' typeWithoutFunction ('.' identifier)? arguments | |
| 159 redirectingFactoryConstructorSignature: // CHANGED | |
| 160 'const'? 'factory' identifier ('.' identifier)? | |
| 161 formalParameterList '=' typeWithoutFunction ('.' identifier)? | |
| 162 ``` | |
| 163 | |
| 164 The syntax relies on treating `Function` as a fixed element in a function | |
| 165 type, similar to a keyword or a symbol (many languages use symbols like | |
| 166 `->` to mark function types). | |
| 167 | |
| 168 *The rationale for using this form is that it makes a function type very | |
| 169 similar to the header in a declaration of a function with that type: Just | |
| 170 replace `Function` by the name of the function, and add missing parameter | |
| 171 names and default values.* | |
| 172 | |
| 173 *The syntax differs from the existing function type syntax | |
| 174 (`functionSignature`) in that the existing syntax allows the type of a | |
| 175 parameter to be omitted, but the new syntax allows parameter names to be | |
| 176 omitted. The rationale for this change is that a function type where a | |
| 177 parameter has a specified name and no type is very likely to be a | |
| 178 mistake. For instance, `int Function(int)` should not be the type of a | |
| 179 function that accepts an argument named "int" of type `dynamic`, it should | |
| 180 specify `int` as the parameter type and allow the name to be | |
| 181 unspecified. It is still possible to opt in and specify the parameter name, | |
| 182 which may be useful as documentation, e.g., if several arguments have the | |
| 183 same type.* | |
| 184 | |
| 185 The modification of the rule for the nonterminal `type` may cause parsing | |
| 186 ambiguities. We intend to handle them by the following disambiguation rule | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
intend to handle -> handle
This is a specificatio
eernst
2017/07/11 15:50:04
Right, `intend` must go.
But I think the main iss
Lasse Reichstein Nielsen
2017/07/12 12:08:06
As discussed offline, there is a difference betwee
| |
| 187 in the parser: If the parser is at a location L where the tokens starting | |
| 188 at L may be a `type` or some other construct (e.g., in the body of a | |
| 189 method, when parsing something that may be a statement and may also be a | |
| 190 declaration), the parser can commit to parsing a type by detecting that it | |
| 191 is looking at the identifier `Function` followed by `<` or `(`, or that it | |
| 192 is looking at a type followed by the identifier `Function` followed by `<` | |
| 193 or `(`. | |
| 194 | |
| 195 *Note that this disambiguation rule does require parsers to have unlimited | |
| 196 lookahead. However, if a "diet parsing" strategy is used where the token | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
Drop "diet parsing", just say "parsing strategy".
eernst
2017/07/11 15:50:04
Done.
| |
| 197 stream already contains references from each opening bracket (such as `<` | |
| 198 or `(`) to the corresponding closing bracket then the decision can be | |
| 199 taken in a fixed number of steps: If the current token is `Function` then | |
| 200 check the immediate successor (`<` or `(` means yes, we are looking at | |
| 201 a `type`, everything else means no) and we're done; if the first token is | |
| 202 an `identifier` other than `Function` then we can check whether it is a | |
| 203 `qualified` by looking at no more than the two next tokens, and we may then | |
| 204 check whether the next token again is `<`; if it is not then we look for | |
| 205 `Function` and the token after that, and if it is `<` then look for the | |
| 206 corresponding `>` (we have now skipped a generic class type), and then | |
| 207 the successor to that token again must be `Function`, and we finally check | |
| 208 its successor (looking for `<` or `(` again). This skips over the | |
| 209 presumed type arguments to a generic class type without checking that they | |
| 210 are actually type arguments, but we conjecture that there are no | |
| 211 syntactically correct alternatives (for example, we conjecture that there | |
| 212 is no syntactically correct statement, not a declaration, starting with | |
| 213 `SomeIdentifier<...> Function(...` where the angle brackets are balanced).* | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:52
It's not clear to me where we allow parsing Functi
eernst
2017/07/11 15:50:05
The words `the tokens starting at L may be a \`typ
Lasse Reichstein Nielsen
2017/07/12 12:08:06
That's actually not what I'm saying - this does us
eernst
2017/07/12 13:15:38
As discussed IRL, there is no finite bound on the
Lasse Reichstein Nielsen
2017/07/13 06:57:17
Agree.
It's worth recognizing that
SomeIdentif
| |
| 214 | |
| 215 *Note that this disambiguation rule will prevent parsing some otherwise | |
| 216 correct programs. For instance, the declaration of an asynchronous function | |
| 217 named `Function` with an omitted return type (meaning `dynamic`) and an | |
| 218 argument named `int` of type `dynamic` using `Function(int) async {}` will | |
| 219 be a parse error, because the parser will commit to parsing a type after | |
| 220 having seen "`Function(`" as a lookahead. However, we do not expect that it | |
| 221 will be a serious problem for developers to be unable to write such | |
| 222 programs.* | |
| 223 | |
| 224 ## Scoping | |
| 225 | |
| 226 Consider a typedef declaration as introduced by this feature, i.e., a | |
| 227 construct on the form | |
| 228 | |
| 229 ``` | |
| 230 metadata 'typedef' identifier typeParameters? '=' functionType ';' | |
| 231 ``` | |
| 232 | |
| 233 This declaration introduces `identifier` into the enclosing library scope. | |
| 234 | |
| 235 Consider a parameterized typedef, i.e., a construct on the form | |
| 236 | |
| 237 ``` | |
| 238 metadata 'typedef' identifier typeParameters '=' functionType ';' | |
| 239 ``` | |
| 240 | |
| 241 Note that in this case the `typeParameters` are present, not optional. This | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:52
Drop the "not optional". Here you are giving a con
eernst
2017/07/11 15:50:04
Dropped `optional`, that's clearly better.
By the
| |
| 242 construct introduces a scope known as the *typedef scope*. Each typedef | |
| 243 scope is nested inside the library scope of the enclosing library. Every | |
| 244 formal type parameter declared by the `typeParameters` in this construct | |
| 245 introduces a type variable into its enclosing typedef scope. The typedef | |
| 246 scope is the current scope for the `typeParameters` themselves, and for the | |
| 247 `functionType`. | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:52
I'm not sure it's worth it to avoid adding a typed
eernst
2017/07/11 15:50:04
Right. It'll be a small change. I'm not doing it n
| |
| 248 | |
| 249 Consider a `functionType` specifying a generic function type, i.e., a | |
| 250 construct on the form | |
| 251 | |
| 252 ``` | |
| 253 returnType? 'Function' typeParameters parameterTypeList | |
| 254 ``` | |
| 255 | |
| 256 Note again that `typeParameters` are present, not optional. This construct | |
| 257 introduces a scope known as a *function type scope*. The function type | |
| 258 scope is nested inside the current scope for the associated `functionType`. | |
| 259 Every formal type parameter declared by the `typeParameters` introduces a | |
| 260 type variable into its enclosing function type scope. The function type | |
| 261 scope is the current scope for the entire `functionType`. | |
| 262 | |
| 263 *This implies that parameterized typedefs and function types are capable of | |
| 264 specifying F-bounded type parameters, because the type parameters are in | |
| 265 scope in the type parameter list itself.* | |
| 266 | |
| 267 ## Static Analysis | |
| 268 | |
| 269 Consider a typedef declaration as introduced by this feature, i.e., a | |
| 270 construct on the form | |
| 271 | |
| 272 ``` | |
| 273 metadata 'typedef' identifier typeParameters? '=' functionType ';' | |
| 274 ``` | |
| 275 | |
| 276 It is a compile-time error if a name *N* introduced into a library scope by | |
| 277 a typedef has an associated `functionType` which depends directly or | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:52
Define "depends".
I guess it's actually pretty har
eernst
2017/07/11 15:50:04
This is concerned with static analysis, and depend
| |
| 278 indirectly on *N*. It is a compile-time error if a bound on a formal type | |
| 279 parameter in `typeParameters` is not a type. It is a compile-time error if | |
| 280 a typedef has an associated `functionType` which is not a type when | |
| 281 analyzed under the assumption that every identifier resolving to a formal | |
| 282 type parameter in `typeParameters` is a type. It is a compile-time error if | |
| 283 an instantiation *F<T1..Tk>* of a parameterized typedef is mal-bounded. | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
Is it?
We allow super-bounded generic types in so
eernst
2017/07/11 15:50:05
Aha?! I supported super-bounded types and still do
Lasse Reichstein Nielsen
2017/07/12 12:08:06
Agree that it is a problem, and as discussed it do
eernst
2017/07/12 13:15:37
I'd suggest that we use the current behavior of `d
| |
| 284 | |
| 285 *This implies that a typedef cannot be recursive. It can only introduce a | |
| 286 name as an alias for a type which is already expressible as a | |
| 287 `functionType`, or a name for a type-level function F where every | |
| 288 well-bounded invocation `F<T1..Tk>` denotes a type which could be expressed | |
| 289 as a `functionType`. Following | |
| 290 [common terminology](https://en.wikipedia.org/wiki/Kind_(type_theory)), we | |
| 291 could say that a typedef can define entities of kind ` * ` and of kind | |
| 292 ` * -> * `, and, when it is assumed that every formal type parameter of the | |
| 293 typedef (if any) has kind ` * `, it is an error if the right hand side of the | |
| 294 declaration denotes an entity of any other kind than ` * `; in particular, | |
| 295 declarations of entities of kind ` * -> * ` cannot be curried.* | |
|
Lasse Reichstein Nielsen
2017/07/11 11:13:53
Is this important? Does it affect implementation,
eernst
2017/07/11 15:50:05
It is intended to be helpful for people who know "
| |
| 296 | |
| 297 It is a compile-time error if a name declared in a typedef, with or without | |
| 298 actual type arguments, is used as a superclass, superinterface, or mixin. It | |
| 299 is a compile-time error if a generic function type is used as a bound for a | |
| 300 formal type parameter of a class or a function. It is a compile-time error if | |
| 301 a generic function type is used as an actual type argument. | |
| 302 | |
| 303 *Generic function types can thus only be used in the following situations:* | |
| 304 | |
| 305 - *as a type annotation on an local, instance, static, or global variable.* | |
| 306 - *as a function return or parameter type.* | |
| 307 - *in a type test.* | |
| 308 - *in a type cast.* | |
| 309 - *in an on-catch clause.* | |
| 310 - *as a parameter or return type in a function type.* | |
| 311 | |
| 312 *The motivation for having this constraint is that it ensures that the Dart type | |
| 313 system admits only predicative types. It does admit non-prenex types, e.g., | |
| 314 `int Function(T function<T>(T) f)`. From research into functional calculi | |
| 315 it is well-known that impredicative types give rise to undecidable subtyping, | |
| 316 e.g., | |
| 317 [(Pierce, 1993)](http://www2.tcs.ifi.lmu.de/lehre/SS07/Typen/pierce93bounded.pdf ), | |
| 318 and even though the Dart type system is very different from F-sub, we cannot | |
| 319 assume that these difficulties are absent.* | |
| 320 | |
| 321 ## Dynamic Semantics | |
| 322 | |
| 323 The addition of this feature does not change the dynamic semantics of | |
| 324 Dart. | |
| 325 | |
| 326 ## Changes | |
| 327 | |
| 328 2017-May-31: Added constraint on usage of generic function types: They | |
| 329 cannot be used as type parameter bounds nor as type arguments. | |
| 330 | |
| 331 2017-Jan-04: Adjusted the grammar to require named parameter types to have | |
| 332 a type (previously, the type was optional). | |
| 333 | |
| 334 2016-Dec-21: Changed the grammar to prevent the new function type syntax | |
| 335 in several locations (for instance, as a super class or as a mixin). The | |
| 336 main change in the grammar is the introduction of `typeWithoutFunction`. | |
| 337 | |
| 338 2016-Dec-15: Changed the grammar to prevent the old style function types | |
| 339 (derived from `functionSignature` in the grammar) from occurring inside | |
| 340 the new style (`functionType`). | |
| OLD | NEW |