|
| 1 | +# Generic Function Instantiation |
| 2 | + |
| 3 | +Author: eernst@. |
| 4 | + |
| 5 | +Version: 0.3 (2018-04-05) |
| 6 | + |
| 7 | +Status: Under discussion. |
| 8 | + |
| 9 | +**This document** is a Dart 2 feature specification of _generic function |
| 10 | +instantiation_, which is the feature that implicitly coerces a reference to |
| 11 | +a generic function into a non-generic function obtained from said generic |
| 12 | +function by passing inferred type arguments. |
| 13 | + |
| 14 | +Intuitively, this is the feature that provides inference for function |
| 15 | +values, corresponding to the more well-known inference that we may get for |
| 16 | +each invocation of a generic function: |
| 17 | + |
| 18 | +```dart |
| 19 | +List<T> f<T>(T t) => [t]; |
| 20 | +
|
| 21 | +void g(Iterable<int> f(int i)) => print(f(42)); |
| 22 | +
|
| 23 | +main() { |
| 24 | + // Invocation inference. |
| 25 | + print(f(42)); // Inferred as `f<int>(42)`. |
| 26 | +
|
| 27 | + // Function value inference. |
| 28 | + g(f); // Inferred approximately as `g((int n) => f<int>(n))`. |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +This document draws on many of the comments on the SDK issue |
| 33 | +[#31665](https://github.com/dart-lang/sdk/issues/31665). |
| 34 | + |
| 35 | + |
| 36 | +## Motivation |
| 37 | + |
| 38 | +The |
| 39 | +[language specification](https://github.com/dart-lang/sdk/blob/master/docs/language/dartLangSpec.tex) |
| 40 | +uses the phrase _function object_ to denote the first-class semantic |
| 41 | +entity which corresponds to a function declaration. In the following |
| 42 | +example, each of the expressions `fg`, `A.fs`, `new A().fi`, and `fl` in |
| 43 | +`main` evaluate to a function object, and so does the function literal at |
| 44 | +the end of the list: |
| 45 | + |
| 46 | +```dart |
| 47 | +int fg(int i) => i; |
| 48 | +
|
| 49 | +class A { |
| 50 | + static int fs(int i) => i; |
| 51 | + int fi(int i) => i; |
| 52 | +} |
| 53 | +
|
| 54 | +main() { |
| 55 | + int fl(int i) => i; |
| 56 | + var functions = |
| 57 | + [fg, A.fs, new A().fi, fl, (int i) => i]; |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +Once a function object has been obtained, it can be passed around by |
| 62 | +assigning it to a variable, passing it as an actual argument, etc. Hence, |
| 63 | +it is the notion of a function object that makes functions first-class |
| 64 | +entities. The computational step that produces a function object from a |
| 65 | +denotation of a function declaration is known as _closurization_. |
| 66 | + |
| 67 | +The situation where closurization occurs is exactly the situation where the |
| 68 | +generic function instantiation feature specified in this document may kick |
| 69 | +in. |
| 70 | + |
| 71 | +First note that generic function declarations provide support for working |
| 72 | +with generic functions as first class values, i.e., generic functions |
| 73 | +support regular closurization, just like non-generic functions. |
| 74 | + |
| 75 | +The essence of generic function instantiation is to allow for "curried" |
| 76 | +invocations, in the sense that a generic function can receive its actual |
| 77 | +type arguments separately during closurization (it must then receive _all_ |
| 78 | +its type arguments, not just some of them), and that yields a non-generic |
| 79 | +function whose type is obtained by substituting type variables in the |
| 80 | +generic type for the actual type arguments: |
| 81 | + |
| 82 | +```dart |
| 83 | +X fg<X extends num>(X x) => x; |
| 84 | +
|
| 85 | +class A { |
| 86 | + static X fs<X extends num>(X x) => x; |
| 87 | + X fi<X extends num>(X x) => x; |
| 88 | +} |
| 89 | +
|
| 90 | +main() { |
| 91 | + X fl<X extends num>(X x) => x; |
| 92 | + var genericFunctions = |
| 93 | + <Function>[fg, A.fs, new A().fi, fl, <X>(X x) => x]; |
| 94 | + var instantiatedFunctions = |
| 95 | + <int Function(int)>[fg, A.fs, new A().fi, fl]; |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +The functions stored in `instantiatedFunctions` are all of type |
| 100 | +`int Function(int)`, and they are obtained by passing the actual |
| 101 | +type argument `int` to the denoted generic function, thus obtaining |
| 102 | +a non-generic function of the specified type. Hence, the reason why |
| 103 | +`instantiatedFunctions` can be created as shown is that it relies on |
| 104 | +generic function instantiation, for each element in the list. |
| 105 | + |
| 106 | +Note that generic function instantiation is not supported with all kinds of |
| 107 | +generic functions; this is discussed in the discussion section. |
| 108 | + |
| 109 | +It may seem natural to allow explicit instantiations, e.g., `fg<int>` and |
| 110 | +`new A().fi<int>` (where type arguments are passed explicitly, but there is |
| 111 | +no value argument list). This kind of construct would yield non-generic |
| 112 | +functions, just like the cases shown above where the type arguments are |
| 113 | +inferred. This is a language extension which is not included in this |
| 114 | +document. It may or may not be added to the language separately. |
| 115 | + |
| 116 | + |
| 117 | +## Syntax |
| 118 | + |
| 119 | +This feature does not affect the grammar. |
| 120 | + |
| 121 | +*If this feature is generalized to include explicit generic |
| 122 | +function instantiation, the grammar would need to be extended |
| 123 | +to allow a construct like `f<int>` as an expression.* |
| 124 | + |
| 125 | + |
| 126 | +## Static Analysis and Program Transformation |
| 127 | + |
| 128 | +We say that a reference of the form `identifier`, |
| 129 | +`identifier '.' identifier`, or |
| 130 | +`identifier '.' identifier '.' identifier` |
| 131 | +is a _statically resolved reference to a function_ if it denotes a |
| 132 | +declaration of a library function or a static function. |
| 133 | + |
| 134 | +*Such a reference is first-order in the sense that it is bound directly to |
| 135 | +the function declaration and there need not be a heap object which |
| 136 | +represents said function declaration in order to support invocations of the |
| 137 | +function. In that sense we may consider statically resolved references |
| 138 | +"extra simple", compared to general references to functions. In particular, |
| 139 | +a statically resolved reference to a function will have a static type which |
| 140 | +is obtained directly from its declaration, it will never be a supertype |
| 141 | +thereof such as `Function` or `dynamic`.* |
| 142 | + |
| 143 | +When an expression _e_ whose static type is a generic function type _G_ is |
| 144 | +used in a context where the expected type is a non-generic function type |
| 145 | +_F_, it is a compile-time error except in the three situations specified |
| 146 | +below. |
| 147 | + |
| 148 | +*The point is that generic function instantiation will only take place in |
| 149 | +situations where we would have a compile-time error without that feature, |
| 150 | +and in those situations the compile-time error will still exist unless the |
| 151 | +situation matches one of those three exceptions.* |
| 152 | + |
| 153 | +**1st exception**: If _e_ is a statically resolved reference to a function, |
| 154 | +and type inference yields an actual type argument list |
| 155 | +_T<sub>1</sub> .. T<sub>k</sub>_ such that |
| 156 | +_G<T<sub>1</sub> .. T<sub>k</sub>>_ is assignable to _F_, then the program |
| 157 | +is modified such that _e_ is replaced by a reference to a non-generic |
| 158 | +function whose signature is obtained by substituting |
| 159 | +_T<sub>1</sub> .. T<sub>k</sub>_ for the formal type parameters in the |
| 160 | +function signature of the function denoted by _e_, and whose semantics for |
| 161 | +each invocation is the same as invoking _e_ with |
| 162 | +_T<sub>1</sub> .. T<sub>k</sub>_ as the actual type argument list. |
| 163 | + |
| 164 | +*Here is an example:* |
| 165 | + |
| 166 | +```dart |
| 167 | +List<T> foo<T>(T t) => [t]; |
| 168 | +List<int> fooOfInt(int i) => [i]; |
| 169 | +
|
| 170 | +String bar(List<int> f(int)) => "${f(42)}"; |
| 171 | +
|
| 172 | +main() { |
| 173 | + print(bar(foo)); |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +*In this example, `foo` as an actual argument to `bar` will be modified as |
| 178 | +if the call had been `bar(fooOfInt)`, except for equality—which is |
| 179 | +specified next.* |
| 180 | + |
| 181 | +Consider two distinct evaluations of a statically resolved reference to the |
| 182 | +same generic function, which are subject to the above-mentioned |
| 183 | +transformation with the same actual type argument list, and let `f1` and |
| 184 | +`f2` denote the two functions obtained after the transformation. It is then |
| 185 | +guaranteed that `f1 == f2` evaluates to true, but `identical(f1, f2)` can |
| 186 | +be false or true, depending on the implementation. |
| 187 | + |
| 188 | +**2nd exception**: Generic function instantiation is supported for instance |
| 189 | +methods as well as statically resolved functions: If |
| 190 | + |
| 191 | +- _e_ is a property extraction which denotes a closurization, |
| 192 | +- the static type of _e_ is a generic function type _G_, |
| 193 | +- _e_ occurs in a context where the expected type is a non-generic |
| 194 | + function type _F_, and |
| 195 | +- type inference yields an actual type argument list |
| 196 | + _T<sub>1</sub> .. T<sub>k</sub>_ such that |
| 197 | + _G<T<sub>1</sub> .. T<sub>k</sub>>_ is assignable to _F_ |
| 198 | + |
| 199 | +then the program is modified such that _e_ is replaced by a reference to a |
| 200 | +non-generic function whose signature is obtained by substituting |
| 201 | +_T<sub>1</sub> .. T<sub>k</sub>_ for |
| 202 | +the formal type parameters in the signature of the method denoted by |
| 203 | +_e_, and whose semantics for each invocation is the same as |
| 204 | +invoking that method on that receiver with |
| 205 | +_T<sub>1</sub> .. T<sub>k</sub>_ as the actual type argument list. |
| 206 | + |
| 207 | +*Note that the statically known declaration of the method which is |
| 208 | +closurized may not be the same one as the declaration of the method which |
| 209 | +is actually closurized at run time, but it is guaranteed that the actual |
| 210 | +signature will have a formal type parameter list with the same length, |
| 211 | +where each formal type parameter will have the same bound as the statically |
| 212 | +known one, and the value parameters will have types which are in a correct |
| 213 | +override relationship to the statically known ones. In other words, the |
| 214 | +function obtained by generic function instantiation on an instance method |
| 215 | +may accept a different number of parameters, with type annotations that are |
| 216 | +different than the statically known ones, but the corresponding function |
| 217 | +type will be a subtype of the statically known one, i.e., it can be called |
| 218 | +safely. (It is possible that the method which is actually closurized has |
| 219 | +one or more formal parameters which are covariant, and this may cause an |
| 220 | +otherwise statically safe invocation to fail at run-time, but this is |
| 221 | +exactly the same situation as we would have had with a direct invocation of |
| 222 | +the method.)* |
| 223 | + |
| 224 | +Consider two distinct evaluations of a property extraction for the same method |
| 225 | +of receivers `o1` and `o2`, which are subject to the above-mentioned |
| 226 | +transformation with the same actual type argument list, and let `f1` and |
| 227 | +`f2` denote the two functions obtained after the transformation. It is then |
| 228 | +guaranteed that `f1 == f2` evaluates to the same value as `identical(o1, o2)`, |
| 229 | +but `identical(f1, f2)` can be false or true, depending on the implementation. |
| 230 | + |
| 231 | +*Here is an example:* |
| 232 | + |
| 233 | +```dart |
| 234 | +class A { |
| 235 | + List<T> foo<T>(T t) => [t]; |
| 236 | +} |
| 237 | +
|
| 238 | +String bar(List<int> f(int)) => "${f(42)}"; |
| 239 | +
|
| 240 | +main() { |
| 241 | + print(bar(new A().foo)); |
| 242 | +} |
| 243 | +``` |
| 244 | + |
| 245 | +*In this example, `new A().foo` as an actual argument to `bar` will be |
| 246 | +modified as if the call had been `bar((int i) => o.foo<int>(i))` where `o` |
| 247 | +is a fresh variable bound to the result of evaluating `new A()`, except for |
| 248 | +equality.* |
| 249 | + |
| 250 | +**3rd exception**: Generic function instantiation is supported also for |
| 251 | +local functions: If |
| 252 | + |
| 253 | +- _e_ is an `identifier` denoting a local function, |
| 254 | +- the static type of _e_ is a generic function type _G_, |
| 255 | +- _e_ occurs in a context where the expected type is a non-generic |
| 256 | + function type _F_, and |
| 257 | +- type inference yields an actual type argument list |
| 258 | + _T<sub>1</sub> .. T<sub>k</sub>_ such that |
| 259 | + _G<T<sub>1</sub> .. T<sub>k</sub>>_ is assignable to _F_ |
| 260 | + |
| 261 | +then the program is modified such that _e_ is replaced by a reference to a |
| 262 | +non-generic function whose signature is obtained by substituting |
| 263 | +_T<sub>1</sub> .. T<sub>k</sub>_ for |
| 264 | +the formal type parameters in the signature of the function denoted by _e_, |
| 265 | +and whose semantics for each invocation is the same as invoking that |
| 266 | +function on that receiver with _T<sub>1</sub> .. T<sub>k</sub>_ as the |
| 267 | +actual type argument list. |
| 268 | + |
| 269 | +*No special guarantees are provided regarding the equality and identity |
| 270 | +properties of the non-generic functions obtained from a local function.* |
| 271 | + |
| 272 | +If _e_ is an expression which is subject to generic function instantiation |
| 273 | +as specified above, and the function denoted by _e_ is a top-level function |
| 274 | +or a static method that is not qualified by a deferred prefix, and the |
| 275 | +inferred type arguments are all compile-time constant type expressions |
| 276 | +(*cf. [this CL](https://dart-review.googlesource.com/c/sdk/+/36220)*), then |
| 277 | +_e_ is a constant expression. Other than that, an expression subject to |
| 278 | +generic function instantiation is not constant. |
| 279 | + |
| 280 | + |
| 281 | +## Dynamic Semantics |
| 282 | + |
| 283 | +The dynamic semantics of this feature follows directly from the fact that |
| 284 | +the section on static analysis specifies which expressions are subject to |
| 285 | +generic function instantiation, and how to obtain the non-generic function |
| 286 | +which is the value of such an expression. |
| 287 | + |
| 288 | +There is one exception: It is possible for inference to provide a type |
| 289 | +argument which is not statically guaranteed to satisfy the declared upper |
| 290 | +bound. In that case, a dynamic error occurs when the generic function |
| 291 | +instantiation takes place. |
| 292 | + |
| 293 | +*Here is an example to illustrate how this may occur:* |
| 294 | + |
| 295 | +```dart |
| 296 | +class C<X> { |
| 297 | + X x; |
| 298 | + void foo<Y extends X>(Y y) => x = y; |
| 299 | +} |
| 300 | +
|
| 301 | +C<num> complexComputation() => new C<int>(); |
| 302 | +
|
| 303 | +main() { |
| 304 | + C<num> c = complexComputation(); |
| 305 | + void Function(num) f = c.foo; // Inferred type argument: `num`. |
| 306 | +} |
| 307 | +``` |
| 308 | + |
| 309 | +*In this situation, the inferred type argument `num` is not guaranteed to |
| 310 | +satisfy the declared upper bound of `Y`, because the actual type argument |
| 311 | +of `c`, let us call it `T`, is only known to be some subtype of `num`. |
| 312 | +There is no way to denote the type `T` or any other type (except `Null`) |
| 313 | +which is guaranteed to be a subtype of `T`. Hence, the chosen type argument |
| 314 | +may turn out to violate the bound at run time, and that violation must be |
| 315 | +detected when the tear-off takes place, rather than letting the tear-off |
| 316 | +succeed and incurring a dynamic error at each invocation of the resulting |
| 317 | +function object.* |
| 318 | + |
| 319 | + |
| 320 | +## Discussion |
| 321 | + |
| 322 | +There is no support for generic function instantiation with function |
| 323 | +literals. That is hardly a serious omission, however, because a function |
| 324 | +literal is only referred from one single location (the place where it |
| 325 | +occurs), and hence there is never a need to use such a function both as a |
| 326 | +generic and as a non-generic function, so it is extremely likely to be |
| 327 | +simpler and more convenient to write the function literal as a non-generic |
| 328 | +function in the first place, if that is how it will be used. |
| 329 | + |
| 330 | +```dart |
| 331 | +class A<X> { |
| 332 | + X x; |
| 333 | +
|
| 334 | + A(this.x); |
| 335 | +
|
| 336 | + void f(List<X> Function(X) g) => print(g(x)); |
| 337 | +
|
| 338 | + void bar() { |
| 339 | + // Error: Needs generic function instantiation, |
| 340 | + // which would implicitly pass `<X>`. |
| 341 | + f(<Y>(Y y) => [y]); |
| 342 | +
|
| 343 | + // Work-around: Just use a non-generic function---it can get |
| 344 | + // the required different types for different values of `X` by |
| 345 | + // using `X` directly. |
| 346 | + f((X x) => [x]); |
| 347 | + } |
| 348 | +} |
| 349 | +
|
| 350 | +main() { |
| 351 | + new A<int>(42).bar(); |
| 352 | +} |
| 353 | +``` |
| 354 | + |
| 355 | +Finally, there is no support for generic function instantiation with first |
| 356 | +class functions (e.g., the value of a variable or an actual argument). This |
| 357 | +choice was made in order to avoid the complexity and performance |
| 358 | +implications of having such a feature. Note that, apart from the `==` |
| 359 | +property, it is always possible to write a function literal in order to |
| 360 | +pass actual type arguments explicitly, thus getting the same effect: |
| 361 | + |
| 362 | +```dart |
| 363 | +List<T> foo<T>(T t) => [t]; |
| 364 | +
|
| 365 | +void g(List<int> Function(int) h) => print(h(42)[0].isEven); |
| 366 | +
|
| 367 | +void bar(List<T> Function<T>(T) f) { |
| 368 | + g(f); // Error: Generic function instantiation not supported here. |
| 369 | + // Work-around. |
| 370 | + g((int i) => f(i)); |
| 371 | +} |
| 372 | +
|
| 373 | +main() { |
| 374 | + bar(foo); // No generic function instantiation needed here. |
| 375 | +} |
| 376 | +``` |
| 377 | + |
| 378 | + |
| 379 | +## Revisions |
| 380 | + |
| 381 | +- 0.3 (2018-04-05) Clarified constancy of expressions subject to generic |
| 382 | + function instantiation. |
| 383 | + |
| 384 | +- 0.2 (2018-03-21) Adjusted to include support for generic function |
| 385 | + instantiation also for local functions. |
| 386 | + |
| 387 | +- 0.1 (2018-03-19) Initial version. |
0 commit comments