さて、上の例では整数を葉とする木を定義しましたが、もし浮動小数を葉とする木、文字列を葉とする木、整数の組を葉とする木…等々も定義したくなったら、別々に定義するのはいかにも非効率的ですし、「同じようなことをしているコードは一つにまとめる」というプログラミングの鉄則にも違反します。
そこでMLでは「任意の型αについて、αを葉とする木」のような多相データ型を定義することができます。このαのことを型変数といいます。ただし普通の環境でαのようなギリシャ文字を入力するのは面倒なので、'aのように表記するのが通常です。
# type 'a tree = Leaf of 'a | Node of 'a tree * 'a tree ;;
type 'a tree = Leaf of 'a | Node of 'a tree * 'a tree
# Leaf 123 ;;
- : int tree = Leaf 123
# Node(Leaf 4.56, Leaf 78.9) ;;
- : float tree = Node (Leaf 4.56, Leaf 78.9)
# Node(Leaf "abc", Node(Leaf "def", Leaf
"ghi")) ;;
- : string tree = Node (Leaf "abc", Node (Leaf
"def", Leaf "ghi"))
このようなデータ型に対して、たとえば木の高さを求める関数heightは次のように書くことができます。
# let rec height = function
Leaf _ -> 0
|
Node(l, r) -> 1 + max (height l) (height r) ;;
val height : 'a tree -> int = <fun>
Leaf _の_はダミーのパターンで、"don't care"と呼ばれます。とにかくLeafであれば高さ0なので、中身は「気にしない」というわけです。Nodeの場合は、左の枝と右の枝の高さの大きいほう(max)に1を足して返します。この関数は、どんな値を葉に持つ木でも受け取れるので、'a tree -> intという多相的な関数型を与えられます。このような関数のことを多相関数といいます。
一般に木やリストといった、要素の集まりを表すコンテナの定義では、このような多相関数・多相データ構造が特に役に立ちます。ちなみにC++のSTL(標準テンプレートライブラリ)は、まさに多相関数や多相データ構造のライブラリなのですが、プログラマが複雑な型を書かないといけなかったり、マクロのようにインライン展開してからコンパイル時チェックが行われるので、エラーメッセージがとてもわかりにくかったりします。MLでは、複雑な多相関数や高階関数の型も自動で決定されますし(型推論といいます)、きちんと別々に型チェックされるので、C++のような問題はありません。