Ibis のバリアントとパターンマッチ

パターンマッチは必須か

Ibis にバリアント型を実装する際問題となったのが、バリアントをどのように分解するかです。多くの関数型言語ではパターンマッチを使いますが、パターンのパーズやさまざまなデータ型への対応が必要となるため、少々面倒です。Ibis では実装の容易さを優先するため、通常のパターンマッチとは異なる手法を採用しました。

最低限のパターンマッチ

Ibis で採用した手法は以下のとおりです。

  1. バリアントはタグとただひとつの値を保持する。
  2. パターンマッチはタグによって分岐し、保持していた値に関数を適用する。
  3. パターンマッチの対象はバリアントのみ

例えば、

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 流パターンマッチの実用上のメリットはほとんどありません。ただ、小さなプリミティブで新しいデータ型の定義というそれなりに高度な機能が実現できるのは、言語を作るのが趣味の人間として魅力を感じるところです。