Generic Types

Adding abstract (polymorphic) types using 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.

1
2
3
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.

1
2
3
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.

1
2
3
function identity<T>(value: T): T {
  return value;
}

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.

In the following example, we forget to properly annotate identity with a generic type, so we run into trouble when we try to assign it to func. On the other hand, genericIdentity is properly typed, and we are able to use it as expected.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// @flow

type IdentityWrapper = {
  func<T>(T): T
}

function identity(value) {
  return value;
}

function genericIdentity<T>(value: T): T {
  return value;
}

// $ExpectError
const bad: IdentityWrapper = { func: identity }; // Error!
const good: IdentityWrapper = { func: genericIdentity }; // Works!
Cannot assign object literal to `bad` because `T` [1] is incompatible with `T` [2] in the return value of property `func`.

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).

1
2
3
4
5
6
7
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).

1
<T>(param: T) => T

Which then gets used as its own type.

1
2
3
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.

1
2
3
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).

1
2
3
4
5
6
7
8
9
10
11
class Item<T> {
  prop: T;

  constructor(param: T) {
    this.prop = param;
  }

  method(): T {
    return this.prop;
  }
}
Type aliases with generics
1
2
3
4
type Item<T> = {
  foo: T,
  bar: T,
};
Interfaces with generics
1
2
3
4
interface Item<T> {
  foo: T,
  bar: T,
}
Supplying Type Arguments to Callables

You can give callable entities type arguments for their generics directly in the call:

1
2
3
4
5
6
7
//@flow
function doSomething<T>(param: T): T {
  // ...
  return param;
}

doSomething<number>(3);

You can also give generic classes type arguments directly in the new expression:

1
2
3
//@flow
class GenericClass<T> {}
const 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:

1
2
3
//@flow
class GenericClass<T, U, V>{}
const c = new GenericClass<_, number, _>()

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.

1
2
3
4
5
function constant<T>(value: T): () => 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:

1
2
3
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.

1
2
3
4
5
6
7
8
9
10
11
12
// @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!
}
Cannot return `"foo"` because string [1] is incompatible with `T` [2]. Cannot assign `"foo"` to `value` because string [1] is incompatible with `T` [2]. Cannot return `value` because string [1] is incompatible with `T` [2].

Flow tracks the specific type of the value you pass through a generic, letting you use it later.

1
2
3
4
5
6
7
8
9
// @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);
Cannot assign `identity(...)` to `three` because number [1] is incompatible with number literal `3` [2].

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.

1
2
3
4
5
6
// @flow
function logFoo<T>(obj: T): T {
  // $ExpectError
  console.log(obj.foo); // Error!
  return obj;
}
Cannot get `obj.foo` because property `foo` is missing in `T` [1].

You could refine the type, but the generic will still allow any type to be passed in.

1
2
3
4
5
6
7
8
9
10
// @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.

1
2
3
4
5
6
7
8
9
// @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!
Cannot call `logFoo` with object literal bound to `obj` because property `foo` is missing in object literal [1] but exists in object type [2].

This way you can keep the behavior of generics while only allowing certain types to be used.

1
2
3
4
5
6
7
8
9
// @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");
Cannot call `identity` with `"three"` bound to `value` because string [1] is incompatible with number [2].

Generic types act as bounds

1
2
3
4
5
6
7
// @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.

1
2
3
4
5
6
7
8
// @flow
function identity(val: string): string {
  return val;
}

let foo: 'foo' = 'foo';           // Works!
// $ExpectError
let bar: 'bar' = identity('bar'); // Error!
Cannot assign `identity(...)` to `bar` because string [1] is incompatible with string literal `bar` [2].

Generics allow you to hold onto the more specific type while adding a constraint. In this way types on generics act as “bounds”.

1
2
3
4
5
6
7
// @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.

1
2
3
4
5
6
7
8
9
// @flow
function identity<T: string>(val: T): T {
  let str: string = val; // Works!
  // $ExpectError
  let bar: 'bar'  = val; // Error!
  return val;
}

identity('bar');
Cannot assign `val` to `bar` because string [1] is incompatible with string literal `bar` [2].

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.

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
// @flow
class Item<T> {
  prop: T;
  constructor(param: T) {
    this.prop = param;
  }
}

let item1: Item<number> = new Item(42); // Works!
// $ExpectError
let item2: Item = new Item(42); // Error!
Cannot use `Item` [1] without 1 type argument.

Type Aliases

1
2
3
4
5
6
7
8
// @flow
type Item<T> = {
  prop: T,
};

let item1: Item<number> = { prop: 42 }; // Works!
// $ExpectError
let item2: Item = { prop: 42 }; // Error!
Cannot use `Item` [1] without 1 type argument.

Interfaces

1
2
3
4
5
6
7
8
9
10
11
12
// @flow
interface HasProp<T> {
  prop: T,
}

class Item {
  prop: string;
}

(Item.prototype: HasProp<string>); // Works!
// $ExpectError
(Item.prototype: HasProp); // Error!
Cannot use `HasProp` [1] without 1 type argument.
Adding defaults to parameterized generics

You can also provide defaults for parameterized generics just like parameters of a function.

1
2
3
4
5
6
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).

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:

1
2
3
4
5
//@flow
type GenericBox<+T> = T;

var x: GenericBox<number> = 3;
(x: GenericBox<number| string>);

The example above could not be accomplished without the + variance sigil:

1
2
3
4
5
//@flow
type GenericBoxError<T> = T;

var x: GenericBoxError<number> = 3;
(x: GenericBoxError<number| string>); // number | string is not compatible with number.
Cannot cast `x` to `GenericBoxError` because number [1] is incompatible with string [2] in type argument `T` [3].

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:

1
2
//@flow
type NotActuallyCovariant<+T> = (T) => void;
Cannot use `T` [1] in an input position because `T` [1] is expected to occur only in output positions.

Was this guide helpful? Let us know by sending a message to @flowtype.