Fixes https://github.com/dart-lang/sdk/issues/38984 Change-Id: Ic8ef8838b0a8c3233bf94706b243cdd9aedacbc1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/127963 Reviewed-by: Bob Nystrom <rnystrom@google.com> Reviewed-by: Kathy Walrath <kathyw@google.com> Commit-Queue: Kathy Walrath <kathyw@google.com>
5.4 KiB
Using Generic Methods
**Note: This document is out of date. Please see Sound Dart for up-to-date documentation on Dart's type system. The work below was a precursor towards Dart's current type system.
For historical reasons, this feature is called "generic methods", but it applies equally well to instance methods, static methods, top-level functions, local functions, and even lambda expressions.**
Initially a proposal, generic methods are on their way to being fully supported in Dart. Here is how to use them.
When they were still being prototyped, an older comment-based syntax was designed so that the static analysis could be implemented and tested before the VM and compilers needed to worry about the syntax. Now that real syntax is allowed everywhere, this doc has been updated.
Declaring generic methods
Type parameters for generic methods are listed after the method or function name, inside angle brackets:
/// Takes two type parameters, [K] and [V].
Map<K, V> singletonMap<K, V>(K key, V value) {
return <K, V>{ key, value };
}
As with classes, you can put bounds on type parameters:
/// Takes a list of two numbers of some num-derived type [T].
T sumPair<T extends num>(List<T> items) {
return items[0] + items[1];
}
Class methods (instance and static) can be declared to take generic parameters in the same way:
class C {
static int f<S, T>(int x) => 3;
int m<S, T>(int x) => 3;
}
This even works for function-typed parameters, local functions, and function expressions:
/// Takes a generic method as a parameter [callback].
void functionTypedParameter(T callback<T>(T thing)) {}
// Declares a local generic function `itself`.
void localFunction() {
T itself<T>(T thing) => thing;
}
// Binds a generic function expression to a local variable.
void functionExpression() {
var lambda = <T>(T thing) => thing;
}
We do not currently support a way to declare a function as returning a generic
function. This will eventually be supported using a typedef
.
Using generic method type parameters
You've seen some examples already, but you can use a generic type parameter almost anywhere you would expect in a generic method.
-
Inside the method's parameter list:
takeThing<T>(T thing) { ... } // ^-- Here.
-
Inside type annotations in the body of the method:
useThing<T>() { T thing = getThing(); //^-- Here. List<T> pair = [thing, thing]; // ^-- And here. }
-
In the return type of the method:
T itself<T>(T thing) => thing; //^-- Here.
-
As type arguments in generic classes and method calls:
useThing<T>(T thing) { var pair = <T>[thing, thing]; // ^-- Here. var set = new Set<T>()..add(thing); // ^-- And here. }
Note that generic methods are not yet supported at runtime on the VM and dart2js. On those platforms, uses of generic method type arguments are treated like
dynamic
today. So in this example,pair
's reified type at runtime will beList<dynamic>
andset
will beSet<dynamic>
.
There are two places you cannot use a generic method type parameter. Both are because the VM and dart2js don't support reifying generic method type arguments yet. Since these expressions wouldn't do what you want, we've temporarily defined them to be an error:
-
As the right-hand side of an
is
oris!
expression.testType<T>(object) { print(object is T); // ^-- Error! print(object is! T); // ^-- Error! }
-
As a type literal:
printType<T>() { Type t = T; // ^-- Error! print(t); }
Once we have full runtime support for generic methods, these will be allowed.
Calling generic methods
Most of the time, when you call a generic method, you can leave off the type arguments and strong mode's type inference will fill them in for you automatically. For example:
var fruits = ["apple", "banana", "cherry"];
var lengths = fruits.map((fruit) => fruit.length);
The map()
method on Iterable is now generic and takes a type parameter for the
element type of the returned sequence:
class Iterable<T> {
Iterable<S> map<S>(S transform(T element)) { ... }
// Other stuff...
}
In this example, the type checker:
- Infers
List<String>
for the type offruits
based on the elements in the list literal. - That lets it infer
String
for the type of the lambda parameterfruit
passed tomap()
. - Then, from the result of calling
.length
, it infers the return type of the lambda to beint
. - That in turn is used to fill in the type argument to the call to
map()
asint
, and the resulting sequence is anIterable<int>
.
If inference isn't able to fill in a type argument for you, it uses dynamic
instead. If that isn't what you want, or it infers a type you don't want, you
can always pass them explicitly:
// Explicitly give a type so that we don't infer "int".
var lengths = fruits.map<num>((fruit) => fruit.length).toList();
// So that we can later add doubles to the result.
lengths.add(1.2);