On this page

OCaml Parametric Polymorphism — 'a list, Generic Functions

Published on July 21, 2026

Enrico Hochkoeppler Profile

By Enrico Hochkoeppler

Web Developer & TutoriaLibre Founder

A parametrically polymorphic function is one written to work identically across every possible type, rather than being tied to any particular one. 'a, the type variable seen throughout 'a list, 'a option, and countless function signatures, is the mechanism behind this: a placeholder standing for "any type," letting a single definition serve every type that placeholder could stand for, all without a single explicit type annotation.

The Simplest Possible Example​

Consider a function that returns its argument unchanged:


let id x = x

# id;;

- : 'a -> 'a = <fun>

# id 5;;

- : int = 5

# id "hello";;

- : string = "hello"

# id true;;

- : bool = true

id works on an int, a string, and a bool without being rewritten for any of them, because its body never does anything that would require x to be one specific type. The 'a in its signature reads as "for any type 'a, this function maps a value of that type back to a value of the same type" — sometimes written with an explicit universal quantifier, ∀α. α → α, in more formal or academic treatments of the same idea.

The Trade-Off: Genericity in Exchange for Restriction​

Parametric polymorphism comes with a built-in, useful restriction: if a function genuinely doesn't know what concrete type it's working with, it cannot do anything to a value that depends on that type. Trying to violate this is a compile-time error, not a runtime one:


let broken (x : 'a) : 'a = x + 1

Error: This expression has type int but an expression was expected of type 'a

x + 1 requires x to be an int, but the whole premise of 'a is that it might be anything at all — a string, a bool, a function — and + simply isn't defined for those. This restriction is precisely what makes a polymorphic function's behavior predictable: knowing only that a function has type 'a -> 'a, without seeing its implementation at all, is already enough to know that it can't inspect, compare, or transform its argument in any type-specific way — it can only return it, store it, or pass it along unchanged.

Parametric vs. Ad Hoc Polymorphism​

It's worth being precise about which kind of polymorphism this is, since the word "polymorphism" covers more than one distinct idea across programming languages. Ad hoc polymorphism — more commonly known as operator or function overloading — lets the same operator or function name behave differently depending on the type it's given, with a separate implementation chosen for each type. Parametric polymorphism is different: it's one single implementation, working identically no matter which type it's applied to.

OCaml deliberately supports the second kind and not the first. This is exactly why + only ever means integer addition and +. is needed for floats — OCaml doesn't have a mechanism that would let a single + symbol dispatch to different behavior depending on its arguments' types. A genuinely generic numeric function that adds either two int values or two float values with a single definition isn't expressible with ordinary parametric polymorphism at all, since the underlying operation itself differs by type; that specific problem is what OCaml's module system, covered separately, is designed to solve.

Parameterized Types: 'a list, 'a option, and Beyond​

The same 'a placeholder that appears in function types also appears directly in type definitions, producing type constructors rather than fixed types: 'a list isn't one type, it's a whole family of types — int list, string list, (int * bool) list — parameterized by whatever 'a gets replaced with. 'a option works the same way, as does any custom type defined with a leading type parameter.

Functions operating over these parameterized types are typically polymorphic themselves, for exactly the same reason id was:


# List.length;;

- : 'a list -> int = <fun>

# List.rev;;

- : 'a list -> 'a list = <fun>

List.length doesn't care what a list's elements are — only how many of them there are — so its 'a never gets pinned down to anything concrete, and the function works identically on a list of integers or a list of arbitrarily complicated records. List.rev, which reverses a list, similarly never needs to inspect an individual element to do its job, so it stays polymorphic too, with its 'a appearing on both sides of the arrow to guarantee the output is a list of the exact same element type as the input.

Writing a Generic Function from Scratch​

Reaching for 'a explicitly is rarely necessary — inference produces it automatically whenever a function's body doesn't force a more specific type — but it's useful to write one deliberately to see the pattern directly. Here's a function that swaps the two components of a pair, regardless of what those components are:


let swap (a, b) = (b, a)

# swap;;

- : 'a * 'b -> 'b * 'a = <fun>

# swap (1, "one");;

- : string * int = ("one", 1)

# swap (true, 3.14);;

- : float * bool = (3.14, true)

Notice that swap uses two independent type variables, 'a and 'b, rather than reusing the same one twice — this correctly captures that the two components of the pair are allowed to be different types from each other, while still guaranteeing that whatever type occupied the first slot ends up in the second slot after swapping, and vice versa. This is a second, equally important half of what makes polymorphic type signatures precise: a repeated type variable forces two positions to match, while distinct type variables leave them free to differ.

A Useful Contrast: Typed vs. Untyped Genericity​

Some languages offer something that looks similar to parametric polymorphism on the surface but behaves quite differently underneath — C++ templates being a well-known example. A C++ template function is only checked for correctness once it's actually used with a specific type, meaning a template can compile perfectly well and only fail later, when applied to a type that turns out not to support whatever operation the template performs internally. OCaml's parametric polymorphism is checked once, at the point the polymorphic function itself is defined — the compiler verifies right away that the function's body is valid for every possible type 'a could stand for, not just the types it happens to get tested with later. This is why a genuinely generic OCaml function can be trusted completely from its type signature alone, without needing to check every call site separately.

Key Takeaways​

Found this helpful? Share the knowledge...

... or support this project to keep it alive!

Buy Me a Coffee

Previous

Type Inference

Next

Higher-Order Functions