Skip to main content
Version: Next

Parameteric types

LIGO parametric types are types that can accept values of different types.

For example, you might want to define a type that uses a string as a key and some other type as a value. This example defines a type named binding that is a pair that consists of a string and some other type. Building on the binding type, it defines a type named int_binding that uses an integer as the value and a type named string_binding that uses a string as the value:

type key = string;
type binding<value> = [key, value];
const int_binding : binding<int> = ["Alice", 4];
const string_binding : binding<string> = ["Bob", "cat"];

Polymorphic functions

Polymorphic functions can take advantage of parametric types to accept a wide variety of inputs instead of only a single input type. The inputs must be organized in a similar way, known as uniform polymorphism. A simple example is the identity function, which is a function that returns the value that is passed to it. For example, the identity function for an integer parameter looks like this:

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

To create an identity function for a nat or any other type, normally you would have to write a second function that is fundamentally the same. However, thanks to parametric polymorphism, you can write a single function that works for both cases.

The following polymorphic function accepts a value of any type. It defines a type variable named T, lists that type in the function definition, and uses that type variable as the type of the passed parameter and as the type of the return value:

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

Functions can have more than one type variable in the definition, as in this example:

const map = <A,B>(f: (x:A) => B, l: list<A>) : list<B> => List.map (f,l);

Now the id function works the same way when passed parameters of different types:

const three_int : int = id(3);
const three_string : string = id("three");

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

For example, this function accepts a list of a parameterized type. It uses an internal recursive function to reverse the list:

function rev <T>(xs : list<T>) : list<T> {
const rev = <T>([xs, acc] : [list<T>, list<T>]) : list<T> =>
match(xs) {
when([]): acc;
when([y,...ys]): rev([ys, [y,...acc]])
};
return rev([xs, ([] as list<T>)]);
};

Because the type of element in the list is parameterized, the function works on lists of any type, as in these examples:

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

During compilation, LIGO monomorphises polymorphic function into specific instances, resulting in Michelson code that does not contain polymorphic function declarations.