blob: 0edde25aaa9289bc37141c221d43e806a1d44cbf [file] [log] [blame] [view]
# Using Generic Methods
**Note: 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.
[proposal]: https://github.com/leafpetersen/dep-generic-methods/blob/master/proposal.md
When they were still being prototyped, an [older comment-based syntax was
designed][old] 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.
[old]: GENERIC_METHOD_COMMENTS.md
## Declaring generic methods
Type parameters for generic methods are listed after the method or function
name, inside angle brackets:
```dart
/// 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:
```dart
/// 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:
```dart
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:
```dart
/// 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:
```dart
takeThing<T>(T thing) { ... }
// ^-- Here.
```
* Inside type annotations in the body of the method:
```dart
useThing<T>() {
T thing = getThing();
//^-- Here.
List<T> pair = [thing, thing];
// ^-- And here.
}
```
* In the return type of the method:
```dart
T itself<T>(T thing) => thing;
//^-- Here.
```
* As type arguments in generic classes and method calls:
```dart
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 be `List<dynamic>` and `set` will be `Set<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` or `is!` expression.
```dart
testType<T>(object) {
print(object is T);
// ^-- Error!
print(object is! T);
// ^-- Error!
}
```
* As a type literal:
```dart
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:
```dart
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:
```dart
class Iterable<T> {
Iterable<S> map<S>(S transform(T element)) { ... }
// Other stuff...
}
```
In this example, the type checker:
1. Infers `List<String>` for the type of `fruits` based on the elements in the
list literal.
2. That lets it infer `String` for the type of the lambda parameter `fruit`
passed to `map()`.
3. Then, from the result of calling `.length`, it infers the return type of the
lambda to be `int`.
4. That in turn is used to fill in the type argument to the call to `map()` as
`int`, and the resulting sequence is an `Iterable<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:
```dart
// 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);
```