Version: 0.43.0

# Polymorphism

LIGO supports simple polymorphism when introducing declarations. This allows to write functions parametric on a type that can be later instantiated to concrete types.

## The identity function#

For any given type `t`, there is a canonical function of type `t -> t` (function from `t` to `t`): it takes an argument, and returns it immediately. For instance, we can write the identity function for `int` as follows:

const id = (x: int): int => x;

However, if we would want to use the same function on a different type, such as `nat`, we will need to write a new definition:

const idnat = (x : nat): nat => x;

If we read carefully, we see that there is almost no difference between `id` and `idnat`: it is just the type that changes, but for the rest, the body of the function remains the same.

Thanks to parametric polymorphism, we can write a single function declaration that works for both cases.

const id : <T>((x : T) => T) = x => x;

Here `T` is a type variable which can be generalised. In general, types prefixed with `_` are treated as generalisable.

We can then use this function directly in different types by just regular application:

const three_i : int = id(3);
const three_s : string = id("three");

During compilation, LIGO will monomorphise the polymorphic functions into specific instances, resulting in Michelson code that does not contain polymorphic function declarations anymore.

## Polymorphism with parametric types#

Polymorphism is especially useful when writing functions over parametric types, which include built-in types like lists, sets, and maps.

As an example, we will see how to implement list reversing parametrically on any type, rather than just on lists of a specific type.

Similar to the `id` example, we can introduce a type variable that can be generalised. We will write a direct version of the function using an accumulator, but the reader can experiment with different variations by using `List` combinators.

const rev : <T>((xs : list<T>) => list<T>) = xs => {
const rev : ((p : [list<T>, list<T>]) => list<T>) = ([xs, acc] : [list<T>, list<T>]) : list<T> =>
match(xs, list([
([] : list<T>) => acc,
([x,... xs] : list<T>) => rev([xs, list([x,...acc])])
]));
return rev([xs, (list([]) as list<T>)]);
};

We use an accumulator variable `acc` to keep the elements of the list processed, consing each element on it.

As with the identity function, we can then use it directly in different types:

const lint : list<int> = rev(list([1, 2, 3]));
const lnat : list<nat> = rev(list([(1 as nat), (2 as nat), (3 as nat)]));