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 it’s 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 some incompatible instantiation of `T` string This type is incompatible with some incompatible instantiation of `T` string This type is incompatible with the expected return type of some incompatible instantiation 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!
property `foo` Property not found in object literal

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 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 item: Item<number> = new Item(42); // Works!
// $ExpectError
let item: Item = new Item(42); // Error!
item name is already bound let item 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 item: Item<number> = { prop: 42 }; // Works!
// $ExpectError
let item: Item = { prop: 42 }; // Error!
item name is already bound let item 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).