Generic Types
Generic Types
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.
function identity(value: string): string { return value; }
Instead we can create a generic (or polymorphic type) in our function and use it in place of other types.
function identity<T>(value: T): T { return value; }
Generics can be used within functions, function types, classes, type aliases, and interfaces.
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).
function method<T>(param: T): T { // ... } function<T>(param: T): T { // ... }
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.
function method(func: <T>(param: T) => T) { // ... }
Classes with generics
Classes can create generics by placing the type parameter list before the body of the class.
class Item<T> { // ... }
You can use generics in the same places you’d add any other type in a class (property types and method parameter/return types).
class Item<T> { prop: T; constructor(param: T) { this.prop = param; } method(): T { return this.prop; } }
Type aliases with generics
type Item<T> = { foo: T, bar: T, };
Interfaces with generics
interface Item<T> { foo: T, bar: T, }
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.
function constant<T>(value: T) { return function(): T { return value; }; }
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:
function identity<One, Two, Three>(one: One, two: Two, three: Three) { // ... }
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.
// @flow function identity<T>(value: T): T { // $ExpectError return "foo"; // Error! } function identity<T>(value: T): T { // $ExpectError value = "foo"; // Error! // $ExpectError return value; // Error! }
Flow tracks the specific type of the value you pass through a generic, letting you use it later.
// @flow function identity<T>(value: T): T { return value; } let one: 1 = identity(1); let two: 2 = identity(2); // $ExpectError let three: 3 = identity(42);
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.
// @flow function logFoo<T>(obj: T): T { // $ExpectError console.log(obj.foo); // Error! return obj; }
You could refine the type, but the generic will still allow any type to be passed in.
// @flow function logFoo<T>(obj: T): T { if (obj && obj.foo) { console.log(obj.foo); // Works. } return obj; } logFoo({ foo: 'foo', bar: 'bar' }); // Works. logFoo({ bar: 'bar' }); // Works. :(
Instead, you could add a type to your generic like you would with a function parameter.
// @flow function logFoo<T: { foo: string }>(obj: T): T { console.log(obj.foo); // Works! return obj; } logFoo({ foo: 'foo', bar: 'bar' }); // Works! // $ExpectError logFoo({ bar: 'bar' }); // Error!
This way you can keep the behavior of generics while only allowing certain types to be used.
// @flow function identity<T: number>(value: T): T { return value; } let one: 1 = identity(1); let two: 2 = identity(2); // $ExpectError let three: "three" = identity("three");
Generic types act as bounds
// @flow function identity<T>(val: T): T { return val; } let foo: 'foo' = 'foo'; // Works! let 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.
// @flow function identity(val: string): string { return val; } let foo: 'foo' = 'foo'; // Works! // $ExpectError let bar: 'bar' = identity('bar'); // Error!
Generics allow you to hold onto the more specific type while adding a constraint. In this way types on generics act as “bounds”.
// @flow function identity<T: string>(val: T): T { return val; } let foo: 'foo' = 'foo'; // Works! let 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.
// @flow function identity<T: string>(val: T): T { let str: string = val; // Works! // $ExpectError let bar: 'bar' = val; // Error! return val; } identity('bar');
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.
type Item<T> = { prop: T, } let item: Item<string> = { prop: "value" };
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
// @flow class Item<T> { prop: T; constructor(param: T) { this.prop = param; } } let item: Item<number> = new Item(42); // Works! // $ExpectError let item: Item = new Item(42); // Error!
Type Aliases
// @flow type Item<T> = { prop: T, }; let item: Item<number> = { prop: 42 }; // Works! // $ExpectError let item: Item = { prop: 42 }; // Error!
Interfaces
// @flow interface HasProp<T> { prop: T, } class Item { prop: string; } (Item.prototype: HasProp<string>); // Works! // $ExpectError (Item.prototype: HasProp); // Error!
Adding defaults to parameterized generics
You can also provide defaults for parameterized generics just like parameters of a function.
type Item<T: number = 1> = { prop: T, }; let foo: Item<> = { prop:1 }; let bar: Item<2> = { prop: 2 };
You must always include the brackets <>
when using the type (just like parentheses for a function call).
© 2013–present Facebook Inc.
Licensed under the BSD License.
https://flow.org/en/docs/types/generics