Ibis のバリアントとパターンマッチ
パターンマッチは必須か
Ibis にバリアント型を実装する際問題となったのが、バリアントをどのように分解するかです。多くの関数型言語ではパターンマッチを使いますが、パターンのパーズやさまざまなデータ型への対応が必要となるため、少々面倒です。Ibis では実装の容易さを優先するため、通常のパターンマッチとは異なる手法を採用しました。
最低限のパターンマッチ
Ibis で採用した手法は以下のとおりです。
- バリアントはタグとただひとつの値を保持する。
- パターンマッチはタグによって分岐し、保持していた値に関数を適用する。
- パターンマッチの対象はバリアントのみ
例えば、
type nat = Zero of unit | Succ of nat
というバリアント型 nat の定義では、コンストラクタ Zero によって作られるバリアントは Zero というタグと unit 型の値を保持し、コンストラクタ Succ によって作られるバリアントは Succ というタグと nat 型の値を保持します。
case n of Zero -> f | Succ -> g
という式は、nat 型のバリアント n のタグがもし Zero なら保持していた unit 型の値に f を適用し、Succ なら保持していた nat 型の値に g を適用します。
case n of Zero -> f | else -> g
という式の場合、タグが Zero なら同様ですがそれ以外なら n 自体に g を適用します。
例:自然数の加算
例として、nat 型の値を自然数ととらえ加算する関数 add の定義を挙げます。
let rec add = fun m -> fun n -> case m of Zero -> fun _ -> n | Succ -> fun k -> Succ (add k n)
インデントを整えると、
let rec add = fun m -> fun n -> case m of Zero -> fun _ -> n | Succ -> fun k -> Succ (add k n)
OCamlで等価なものをあえて冗長に書くなら
let rec add = fun m -> fun n -> match m with Zero _ -> n | Succ k -> Succ (add k n)
となります。
Ibis 流パターンマッチの実用上のメリットはほとんどありません。ただ、小さなプリミティブで新しいデータ型の定義というそれなりに高度な機能が実現できるのは、言語を作るのが趣味の人間として魅力を感じるところです。