Parameterized Variants
Variant types may be parameterized on other types. For example,
the intlist
type above could be generalized to provide lists (coded
up ourselves) over any type:
type 'a mylist = Nil | Cons of 'a * 'a mylist
let lst3 = Cons (3, Nil) (* similar to [3] *)
let lst_hi = Cons ("hi", Nil) (* similar to ["hi"] *)
Here, mylist
is a type constructor but not a type: there is no
way to write a value of type mylist
. But we can write value of
type int mylist
(e.g., lst3
) and string mylist
(e.g., lst_hi
).
Think of a type constructor as being like a function, but one that
maps types to types, rather than values to value.
Here are some functions over 'a mylist
:
let rec length : 'a mylist -> int = function
| Nil -> 0
| Cons (_,t) -> 1 + length t
let empty : 'a mylist -> bool = function
| Nil -> true
| Cons _ -> false
Notice that the body of each function is unchanged from its previous
definition for intlist
. All that we changed was the type annotation.
And that could even be omitted safely:
let rec length = function
| Nil -> 0
| Cons (_,t) -> 1 + length t
let empty = function
| Nil -> true
| Cons _ -> false
The functions we just wrote are an example of a language feature
called parametric polymorphism. The functions don't care what the 'a
is in 'a mylist
, hence they are perfectly happy to work
on int mylist
or string mylist
or any other (whatever) mylist
.
The word "polymorphism" is based on the Greek roots "poly" (many) and
"morph" (form). A value of type 'a mylist
could have many forms,
depending on the actual type 'a
.
As soon, though, as you place a constraint on what the type 'a
might be,
you give up some polymorphism. For example,
# let rec sum = function
| Nil -> 0
| Cons(h,t) -> h + sum t;;
val sum : int mylist -> int
The fact that we use the (+)
operator with the head of the list
constrains that head element to be an int
, hence all elements
must be int
. That means sum
must take in an int mylist
, not any other
kind of 'a mylist
.
It is also possible to have multiple type parameters for a parameterized type, in which case parentheses are needed:
# type ('a,'b) pair = {first: 'a; second: 'b};;
# let x = {first=2; second="hello"};;
val x : (int, string) pair = {first = 2; second = "hello"}