# Generic Function Instantiation **Author**: eernst@. **Version**: 0.3 (2018-04-05) **Status**: This document is now background material. For normative text, please consult the language specification. **This document** is a Dart 2 feature specification of _generic function instantiation_, which is the feature that implicitly coerces a reference to a generic function into a non-generic function obtained from said generic function by passing inferred type arguments. Intuitively, this is the feature that provides inference for function values, corresponding to the more well-known inference that we may get for each invocation of a generic function: ```dart List f(T t) => [t]; void g(Iterable f(int i)) => print(f(42)); main() { // Invocation inference. print(f(42)); // Inferred as `f(42)`. // Function value inference. g(f); // Inferred approximately as `g((int n) => f(n))`. } ``` This document draws on many of the comments on the SDK issue [#31665](https://github.com/dart-lang/sdk/issues/31665). ## Motivation The [language specification](https://github.com/dart-lang/sdk/blob/master/docs/language/dartLangSpec.tex) uses the phrase _function object_ to denote the first-class semantic entity which corresponds to a function declaration. In the following example, each of the expressions `fg`, `A.fs`, `new A().fi`, and `fl` in `main` evaluate to a function object, and so does the function literal at the end of the list: ```dart int fg(int i) => i; class A { static int fs(int i) => i; int fi(int i) => i; } main() { int fl(int i) => i; var functions = [fg, A.fs, new A().fi, fl, (int i) => i]; } ``` Once a function object has been obtained, it can be passed around by assigning it to a variable, passing it as an actual argument, etc. Hence, it is the notion of a function object that makes functions first-class entities. The computational step that produces a function object from a denotation of a function declaration is known as _closurization_. The situation where closurization occurs is exactly the situation where the generic function instantiation feature specified in this document may kick in. First note that generic function declarations provide support for working with generic functions as first class values, i.e., generic functions support regular closurization, just like non-generic functions. The essence of generic function instantiation is to allow for "curried" invocations, in the sense that a generic function can receive its actual type arguments separately during closurization (it must then receive _all_ its type arguments, not just some of them), and that yields a non-generic function whose type is obtained by substituting type variables in the generic type for the actual type arguments: ```dart X fg(X x) => x; class A { static X fs(X x) => x; X fi(X x) => x; } main() { X fl(X x) => x; var genericFunctions = [fg, A.fs, new A().fi, fl, (X x) => x]; var instantiatedFunctions = [fg, A.fs, new A().fi, fl]; } ``` The functions stored in `instantiatedFunctions` are all of type `int Function(int)`, and they are obtained by passing the actual type argument `int` to the denoted generic function, thus obtaining a non-generic function of the specified type. Hence, the reason why `instantiatedFunctions` can be created as shown is that it relies on generic function instantiation, for each element in the list. Note that generic function instantiation is not supported with all kinds of generic functions; this is discussed in the discussion section. It may seem natural to allow explicit instantiations, e.g., `fg` and `new A().fi` (where type arguments are passed explicitly, but there is no value argument list). This kind of construct would yield non-generic functions, just like the cases shown above where the type arguments are inferred. This is a language extension which is not included in this document. It may or may not be added to the language separately. ## Syntax This feature does not affect the grammar. *If this feature is generalized to include explicit generic function instantiation, the grammar would need to be extended to allow a construct like `f` as an expression.* ## Static Analysis and Program Transformation We say that a reference of the form `identifier`, `identifier '.' identifier`, or `identifier '.' identifier '.' identifier` is a _statically resolved reference to a function_ if it denotes a declaration of a library function or a static function. *Such a reference is first-order in the sense that it is bound directly to the function declaration and there need not be a heap object which represents said function declaration in order to support invocations of the function. In that sense we may consider statically resolved references "extra simple", compared to general references to functions. In particular, a statically resolved reference to a function will have a static type which is obtained directly from its declaration, it will never be a supertype thereof such as `Function` or `dynamic`.* When an expression _e_ whose static type is a generic function type _G_ is used in a context where the expected type is a non-generic function type _F_, it is a compile-time error except in the three situations specified below. *The point is that generic function instantiation will only take place in situations where we would have a compile-time error without that feature, and in those situations the compile-time error will still exist unless the situation matches one of those three exceptions.* **1st exception**: If _e_ is a statically resolved reference to a function, and type inference yields an actual type argument list _T1 .. Tk_ such that _G1 .. Tk>_ is assignable to _F_, then the program is modified such that _e_ is replaced by a reference to a non-generic function whose signature is obtained by substituting _T1 .. Tk_ for the formal type parameters in the function signature of the function denoted by _e_, and whose semantics for each invocation is the same as invoking _e_ with _T1 .. Tk_ as the actual type argument list. *Here is an example:* ```dart List foo(T t) => [t]; List fooOfInt(int i) => [i]; String bar(List f(int)) => "${f(42)}"; main() { print(bar(foo)); } ``` *In this example, `foo` as an actual argument to `bar` will be modified as if the call had been `bar(fooOfInt)`, except for equality—which is specified next.* Consider two distinct evaluations of a statically resolved reference to the same generic function, which are subject to the above-mentioned transformation with the same actual type argument list, and let `f1` and `f2` denote the two functions obtained after the transformation. It is then guaranteed that `f1 == f2` evaluates to true, but `identical(f1, f2)` can be false or true, depending on the implementation. **2nd exception**: Generic function instantiation is supported for instance methods as well as statically resolved functions: If - _e_ is a property extraction which denotes a closurization, - the static type of _e_ is a generic function type _G_, - _e_ occurs in a context where the expected type is a non-generic function type _F_, and - type inference yields an actual type argument list _T1 .. Tk_ such that _G1 .. Tk>_ is assignable to _F_ then the program is modified such that _e_ is replaced by a reference to a non-generic function whose signature is obtained by substituting _T1 .. Tk_ for the formal type parameters in the signature of the method denoted by _e_, and whose semantics for each invocation is the same as invoking that method on that receiver with _T1 .. Tk_ as the actual type argument list. *Note that the statically known declaration of the method which is closurized may not be the same one as the declaration of the method which is actually closurized at run time, but it is guaranteed that the actual signature will have a formal type parameter list with the same length, where each formal type parameter will have the same bound as the statically known one, and the value parameters will have types which are in a correct override relationship to the statically known ones. In other words, the function obtained by generic function instantiation on an instance method may accept a different number of parameters, with type annotations that are different than the statically known ones, but the corresponding function type will be a subtype of the statically known one, i.e., it can be called safely. (It is possible that the method which is actually closurized has one or more formal parameters which are covariant, and this may cause an otherwise statically safe invocation to fail at run-time, but this is exactly the same situation as we would have had with a direct invocation of the method.)* Consider two distinct evaluations of a property extraction for the same method of receivers `o1` and `o2`, which are subject to the above-mentioned transformation with the same actual type argument list, and let `f1` and `f2` denote the two functions obtained after the transformation. It is then guaranteed that `f1 == f2` evaluates to the same value as `identical(o1, o2)`, but `identical(f1, f2)` can be false or true, depending on the implementation. *Here is an example:* ```dart class A { List foo(T t) => [t]; } String bar(List f(int)) => "${f(42)}"; main() { print(bar(new A().foo)); } ``` *In this example, `new A().foo` as an actual argument to `bar` will be modified as if the call had been `bar((int i) => o.foo(i))` where `o` is a fresh variable bound to the result of evaluating `new A()`, except for equality.* **3rd exception**: Generic function instantiation is supported also for local functions: If - _e_ is an `identifier` denoting a local function, - the static type of _e_ is a generic function type _G_, - _e_ occurs in a context where the expected type is a non-generic function type _F_, and - type inference yields an actual type argument list _T1 .. Tk_ such that _G1 .. Tk>_ is assignable to _F_ then the program is modified such that _e_ is replaced by a reference to a non-generic function whose signature is obtained by substituting _T1 .. Tk_ for the formal type parameters in the signature of the function denoted by _e_, and whose semantics for each invocation is the same as invoking that function on that receiver with _T1 .. Tk_ as the actual type argument list. *No special guarantees are provided regarding the equality and identity properties of the non-generic functions obtained from a local function.* If _e_ is an expression which is subject to generic function instantiation as specified above, and the function denoted by _e_ is a top-level function or a static method that is not qualified by a deferred prefix, and the inferred type arguments are all compile-time constant type expressions (*cf. [this CL](https://dart-review.googlesource.com/c/sdk/+/36220)*), then _e_ is a constant expression. Other than that, an expression subject to generic function instantiation is not constant. ## Dynamic Semantics The dynamic semantics of this feature follows directly from the fact that the section on static analysis specifies which expressions are subject to generic function instantiation, and how to obtain the non-generic function which is the value of such an expression. There is one exception: It is possible for inference to provide a type argument which is not statically guaranteed to satisfy the declared upper bound. In that case, a dynamic error occurs when the generic function instantiation takes place. *Here is an example to illustrate how this may occur:* ```dart class C { X x; void foo(Y y) => x = y; } C complexComputation() => new C(); main() { C c = complexComputation(); void Function(num) f = c.foo; // Inferred type argument: `num`. } ``` *In this situation, the inferred type argument `num` is not guaranteed to satisfy the declared upper bound of `Y`, because the actual type argument of `c`, let us call it `T`, is only known to be some subtype of `num`. There is no way to denote the type `T` or any other type (except `Null`) which is guaranteed to be a subtype of `T`. Hence, the chosen type argument may turn out to violate the bound at run time, and that violation must be detected when the tear-off takes place, rather than letting the tear-off succeed and incurring a dynamic error at each invocation of the resulting function object.* ## Discussion There is no support for generic function instantiation with function literals. That is hardly a serious omission, however, because a function literal is only referred from one single location (the place where it occurs), and hence there is never a need to use such a function both as a generic and as a non-generic function, so it is extremely likely to be simpler and more convenient to write the function literal as a non-generic function in the first place, if that is how it will be used. ```dart class A { X x; A(this.x); void f(List Function(X) g) => print(g(x)); void bar() { // Error: Needs generic function instantiation, // which would implicitly pass ``. f((Y y) => [y]); // Work-around: Just use a non-generic function---it can get // the required different types for different values of `X` by // using `X` directly. f((X x) => [x]); } } main() { new A(42).bar(); } ``` Finally, there is no support for generic function instantiation with first class functions (e.g., the value of a variable or an actual argument). This choice was made in order to avoid the complexity and performance implications of having such a feature. Note that, apart from the `==` property, it is always possible to write a function literal in order to pass actual type arguments explicitly, thus getting the same effect: ```dart List foo(T t) => [t]; void g(List Function(int) h) => print(h(42)[0].isEven); void bar(List Function(T) f) { g(f); // Error: Generic function instantiation not supported here. // Work-around. g((int i) => f(i)); } main() { bar(foo); // No generic function instantiation needed here. } ``` ## Revisions - 0.3 (2018-04-05) Clarified constancy of expressions subject to generic function instantiation. - 0.2 (2018-03-21) Adjusted to include support for generic function instantiation also for local functions. - 0.1 (2018-03-19) Initial version.