Generics
Generics (sometimes referred to as polymorphic types) are a way of abstracting a type away.
Imagine writing the following identity
function which returns whatever value
was passed.
function identity(value) {
return value;
}
We would have a lot of trouble trying to write specific types for this function since it could be anything.
1function identity(value: string): string {2 return value;3}
Instead we can create a generic (or polymorphic type) in our function and use it in place of other types.
1function identity<T>(value: T): T {2 return value;3}
Generics can be used within functions, function types, classes, type aliases, and interfaces.
Warning: Flow does not infer generic types. If you want something to have a generic type, annotate it. Otherwise, Flow may infer a type that is less polymorphic than you expect.
Syntax of generics
There are a number of different places where generic types appear in syntax.
Functions with generics
Functions can create generics by adding the type parameter list <T>
before
the function parameter list.
You can use generics in the same places you'd add any other type in a function (parameter or return types).
1function method<T>(param: T): T {2 return param;3}4
5const f = function<T>(param: T): T {6 return param;7}
Function types with generics
Function types can create generics in the same way as normal functions, by
adding the type parameter list <T>
before the function type parameter list.
You can use generics in the same places you'd add any other type in a function type (parameter or return types).
<T>(param: T) => T
Which then gets used as its own type.
1function method(func: <T>(param: T) => T) {2 // ...3}
Classes with generics
Classes can create generics by placing the type parameter list before the body of the class.
1class Item<T> {2 // ...3}
You can use generics in the same places you'd add any other type in a class (property types and method parameter/return types).
1class Item<T> {2 prop: T;3
4 constructor(param: T) {5 this.prop = param;6 }7
8 method(): T {9 return this.prop;10 }11}
Type aliases with generics
1type Item<T> = {2 foo: T,3 bar: T,4};
Interfaces with generics
1interface Item<T> {2 foo: T,3 bar: T,4}
Supplying Type Arguments to Callables
You can give callable entities type arguments for their generics directly in the call:
1function doSomething<T>(param: T): T {2 // ...3 return param;4}5
6doSomething<number>(3);
You can also give generic classes type arguments directly in the new
expression:
1class GenericClass<T> {}2const c = new GenericClass<number>();
If you only want to specify some of the type arguments, you can use _
to let flow infer a type for you:
1class GenericClass<T, U=string, V=number>{}2const c = new GenericClass<boolean, _, string>();
Warning: For performance purposes, we always recommend you annotate with concrete arguments when you can.
_
is not unsafe, but it is slower than explicitly specifying the type arguments.
Behavior of generics
Generics act like variables
Generic types work a lot like variables or function parameters except that they are used for types. You can use them whenever they are in scope.
1function constant<T>(value: T): () => T {2 return function(): T {3 return value;4 };5}
Create as many generics as you need
You can have as many of these generics as you need in the type parameter list, naming them whatever you want:
1function identity<One, Two, Three>(one: One, two: Two, three: Three) {2 // ...3}
Generics track values around
When using a generic type for a value, Flow will track the value and make sure that you aren't replacing it with something else.
1function identity<T>(value: T): T {2 return "foo"; // Error! 3}4
5function identity<T>(value: T): T { 6 value = "foo"; // Error! 7 return value; // Error! 8}
2:10-2:14: Cannot return `"foo"` because string [1] is incompatible with `T` [2]. [incompatible-return]5:10-5:17: Cannot declare `identity` [1] because the name is already bound. [name-already-bound]6:11-6:15: Cannot assign `"foo"` to `value` because string [1] is incompatible with `T` [2]. [incompatible-type]7:10-7:14: Cannot return `value` because string [1] is incompatible with `T` [2]. [incompatible-return]
Flow tracks the specific type of the value you pass through a generic, letting you use it later.
1function identity<T>(value: T): T {2 return value;3}4
5let one: 1 = identity(1);6let two: 2 = identity(2);7let three: 3 = identity(42); // Error
7:16-7:27: Cannot assign `identity(...)` to `three` because number [1] is incompatible with number literal `3` [2]. [incompatible-type]
Adding types to generics
Similar to mixed
, generics have an "unknown" type. You're not allowed to use
a generic as if it were a specific type.
1function logFoo<T>(obj: T): T {2 console.log(obj.foo); // Error! 3 return obj;4}
2:19-2:21: Cannot get `obj.foo` because property `foo` is missing in mixed [1]. [incompatible-use]
You could refine the type, but the generic will still allow any type to be passed in.
1function logFoo<T>(obj: T): T {2 if (obj && obj.foo) {3 console.log(obj.foo); // Works.4 }5 return obj;6}7
8logFoo({ foo: 'foo', bar: 'bar' }); // Works.9logFoo({ bar: 'bar' }); // Works. :(
Instead, you could add a type to your generic like you would with a function parameter.
1function logFoo<T: {foo: string, ...}>(obj: T): T {2 console.log(obj.foo); // Works!3 return obj;4}5
6logFoo({ foo: 'foo', bar: 'bar' }); // Works!7logFoo({ bar: 'bar' }); // Error!
7:8-7:21: Cannot call `logFoo` because property `foo` is missing in object literal [1] but exists in object type [2] in type argument `T`. [prop-missing]
This way you can keep the behavior of generics while only allowing certain types to be used.
1function identity<T: number>(value: T): T {2 return value;3}4
5let one: 1 = identity(1);6let two: 2 = identity(2);7let three: "three" = identity("three"); // Error!
7:31-7:37: Cannot call `identity` because string [1] is incompatible with number [2] in type argument `T`. [incompatible-call]
Generic types act as bounds
1function identity<T>(val: T): T {2 return val;3}4
5let foo: 'foo' = 'foo'; // Works!6let bar: 'bar' = identity('bar'); // Works!
In Flow, most of the time when you pass one type into another you lose the original type. So that when you pass a specific type into a less specific one Flow "forgets" it was once something more specific.
1function identity(val: string): string {2 return val;3}4
5let foo: 'foo' = 'foo'; // Works!6let bar: 'bar' = identity('bar'); // Error!
6:18-6:32: Cannot assign `identity(...)` to `bar` because string [1] is incompatible with string literal `bar` [2]. [incompatible-type]
Generics allow you to hold onto the more specific type while adding a constraint. In this way types on generics act as "bounds".
1function identity<T: string>(val: T): T {2 return val;3}4
5let foo: 'foo' = 'foo'; // Works!6let bar: 'bar' = identity('bar'); // Works!
Note that when you have a value with a bound generic type, you can't use it as if it were a more specific type.
1function identity<T: string>(val: T): T {2 let str: string = val; // Works!3 let bar: 'bar' = val; // Error! 4 return val;5}6
7identity('bar');
3:21-3:23: Cannot assign `val` to `bar` because string [1] is incompatible with string literal `bar` [2]. [incompatible-type]
Parameterized generics
Generics sometimes allow you to pass types in like arguments to a function. These are known as parameterized generics (or parametric polymorphism).
For example, a type alias with a generic is parameterized. When you go to use it you will have to provide a type argument.
1type Item<T> = {2 prop: T,3}4
5let item: Item<string> = {6 prop: "value"7};
You can think of this like passing arguments to a function, only the return value is a type that you can use.
Classes (when being used as a type), type aliases, and interfaces all require that you pass type arguments. Functions and function types do not have parameterized generics.
Classes
1class Item<T> {2 prop: T;3 constructor(param: T) {4 this.prop = param;5 }6}7
8let item1: Item<number> = new Item(42); // Works!9let item2: Item = new Item(42); // Error!
9:12-9:15: Cannot use `Item` [1] without 1 type argument. [missing-type-arg]
Type Aliases
1type Item<T> = {2 prop: T,3};4
5let item1: Item<number> = { prop: 42 }; // Works!6let item2: Item = { prop: 42 }; // Error!
6:12-6:15: Cannot use `Item` [1] without 1 type argument. [missing-type-arg]
Interfaces
1interface HasProp<T> {2 prop: T,3}4
5class Item {6 prop: string;7}8
9Item.prototype as HasProp<string>; // Works!10Item.prototype as HasProp; // Error!
10:19-10:25: Cannot use `HasProp` [1] without 1 type argument. [missing-type-arg]
Adding defaults to parameterized generics
You can also provide defaults for parameterized generics just like parameters of a function.
1type Item<T: number = 1> = {2 prop: T,3};4
5let foo: Item<> = { prop: 1 };6let bar: Item<2> = { prop: 2 };
You must always include the brackets <>
when using the type (just like
parentheses for a function call).
Variance Sigils
You can also specify the subtyping behavior of a generic via variance sigils.
By default, generics behave invariantly, but you may add a +
to their
declaration to make them behave covariantly, or a -
to their declaration to
make them behave contravariantly. See our docs on variance
for a more information on variance in Flow.
Variance sigils allow you to be more specific about how you intend to use your generics, giving Flow the power to do more precise type checking. For example, you may want this relationship to hold:
1type GenericBox<+T> = T;2
3const x: GenericBox<number> = 3;4x as GenericBox<number| string>;
The example above could not be accomplished without the +
variance sigil:
1type GenericBoxError<T> = T;2
3const x: GenericBoxError<number> = 3;4x as GenericBoxError<number| string>; // number | string is not compatible with number.
4:1-4:1: Cannot cast `x` to `GenericBoxError` because string [1] is incompatible with number [2] in type argument `T` [3]. [incompatible-cast]
Note that if you annotate your generic with variance sigils then Flow will check to make sure those types only appear in positions that make sense for that variance sigil. For example, you cannot declare a generic type parameter to behave covariantly and use it in a contravariant position:
1type NotActuallyCovariant<+T> = (T) => void;
1:34-1:34: Cannot use `T` [1] in an input position because `T` [1] is expected to occur only in output positions. [incompatible-variance]