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.

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,
}

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) {
  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!
}
string This type is incompatible with the expected return type of T string This type is incompatible with T string This type is incompatible with the expected return type of T

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);
number Expected number literal `3`, got `42` instead number literal `3`

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;
}
property `foo` Property cannot be accessed on T

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!
object literal This type is incompatible with the expected param type of object type

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");
string This type is incompatible with the expected param type of number

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!
string Expected string literal `bar` string literal `bar`

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');
string Expected string literal `bar` string literal `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.

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!
Item Application of polymorphic type needs <list of 1 argument>. (Can use `*` for inferrable ones)

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!
Item Application of polymorphic type needs <list of 1 argument>. (Can use `*` for inferrable ones)

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!
HasProp Application of polymorphic type needs <list of 1 argument>. (Can use `*` for inferrable ones)
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).


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