多相バリアントを使いこなそう(1)
以前OCamlは学習コストが低いといいましたが、私のOCaml学習の中で唯一多相バリアントだけはその意味を理解するのに時間がかかりました。ただ、分かってみると単純な話で、誰かが噛み砕いて説明してくれれば回り道しなくてもよかったのではないかと悔やまれます。また、多相バリアントについて集中的に解説しているWebページもあまりないので、ここで解説してみようと思います。
多相バリアントの基本
「多相」バリアントというくらいですから、多相バリアントは多相的なバリアントなのです。普通のバリアントは多相になりません。例えば普通のバリアント型、
# type card = Joker | Num of int;; type card = Joker | Num of int # type in_data = Str of string | Num of int;; type in_data = Str of string | Num of int
というふたつの型があったとします。最初の型はジョーカーと数字札があるトランプの型、次の型は何かファイルからデータを読み込む時に文字列と数字のデータを統一的に扱うための型です。どちらにもNum of intというタグがあります。さて、この時に、パターンマッチでNum of intを取り出す関数を作るとどうなるでしょうか?
# let get_number = function Num i -> i | _ -> failwith "not a number";; val get_number : in_data -> int = <fun>
get_number関数の型はin_data->intになってしまいました。この関数はcard型でも使えそうですが、残念ながら型が合わないので使えません。この現象が、普通のバリアント型は多相でないという意味です。
一方で、get_number関数が受け取る引数が、`Num of intという多相バリアント型のタグであれば、そのタグがどんな多相バリアント型に属していても関係がなくなります。
# type card = [ `Jorker | `Num of int ];; (* 多相バリアント型 *) type card = [ `Jorker | `Num of int ] # type in_data = [ `Str of string | `Num of int ];; (* 多相バリアント型 *) type in_data = [ `Num of int | `Str of string ] # let get_number = function (* 多相バリアント型の引数を受け取る *) `Num i -> i | _ -> failwith "not a number";; val get_number : [> `Num of 'a ] -> 'a = <fun> # get_number (`Num 3);; (* 多相バリアント型を適用 *) - : int = 3
上のget_number関数では、[> `Num of 'a ] -> aという型付けがされました。この型の意味は、「`Num of 'aというタグを持つ多相バリアント型を受け取って、その`Numタグの引数と同じ型'aの値を返す関数」という事になります*1。どこにも、cardとかin_dataという名前は出てきません。get_number関数を利用する側から見れば、この関数はcardとin_dataという複数の型で適用できるという意味で多相的といえます。
要点
要するに、普通のバリアント型のタグは特定のバリアント型に属するものでしたが、多相バリアント型のタグは特定の多相バリアント型に属さず、同じタグであれば複数の多相バリアント型のタグになり得る(多相)という訳です。
さて、この性質は何がうれしいのか?次回は多相バリアントの応用例を少し紹介してみようと思います。
*1:[の隣に>記号がありますが、この記号の正確な意味は次回に持ち越します