Version: 0.31.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:

let 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:

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

If we see in detail, there is almost no difference between `id` and `idnat`: it's 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.

let id : ((x : _a) => _a) = (x: _a) => x;

Here `_a` 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.

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

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)]));