Switch to infinite scroll (Full book)

Generic functions

define functions working with unresolved types

Generic functions work with unresolved types at the definition site:

function f<T, U>(x: T, y: U) {}

The actual types resolve at the call site, based on the type arguments or based on the values alone:

// T and U resolve to number:
f<number, number>(1, 2)
f(1, 2)

By default, a type T can be of any type and shape (unconstrained). The compiler cannot assume anything about it, similar to unknown or any. Variables of such unconstrained types are virtually unusable, since there are very few operations that work on unknown shapes.

Instead, it's better to constrain the type T (see constrain the unresolved type)

synopsis

  • First, we signal T is a placeholder type, by marking it with brackets
  • By default, it can be any type.
  • We constrain T to be a subset of a given type with extends. (optional)
  • Then, we use T as a regular type.
function identity<T>(x: T) {
    return x
}

the type becomes known at call site

The type becomes known:

  • explicitly through a type argument: the call can be declarative about the type
  • implicitly through the argument's value: the call provides the value and expects its type to be inferred
identity<number>(3)
identity(3)

constrain the unresolved type

We constrain the placeholder type T to conform to another type, possibly a type union:

function identity<T extends string | number>(x: T): T {
    return x
}

The actual type T is narrower than the type union, and is preserved in the function body.

It becomes known at the call site:

identity("Hi") // here, T is the 'Hi' string literal type, not string | number
earlymorning logo

Generic functions

define functions working with unresolved types

Generic functions work with unresolved types at the definition site:

function f<T, U>(x: T, y: U) {}

The actual types resolve at the call site, based on the type arguments or based on the values alone:

// T and U resolve to number:
f<number, number>(1, 2)
f(1, 2)

By default, a type T can be of any type and shape (unconstrained). The compiler cannot assume anything about it, similar to unknown or any. Variables of such unconstrained types are virtually unusable, since there are very few operations that work on unknown shapes.

Instead, it's better to constrain the type T (see constrain the unresolved type)

synopsis

  • First, we signal T is a placeholder type, by marking it with brackets
  • By default, it can be any type.
  • We constrain T to be a subset of a given type with extends. (optional)
  • Then, we use T as a regular type.
function identity<T>(x: T) {
    return x
}

the type becomes known at call site

The type becomes known:

  • explicitly through a type argument: the call can be declarative about the type
  • implicitly through the argument's value: the call provides the value and expects its type to be inferred
identity<number>(3)
identity(3)

constrain the unresolved type

We constrain the placeholder type T to conform to another type, possibly a type union:

function identity<T extends string | number>(x: T): T {
    return x
}

The actual type T is narrower than the type union, and is preserved in the function body.

It becomes known at the call site:

identity("Hi") // here, T is the 'Hi' string literal type, not string | number