CoffeeScriptで代数的データを表現する

関数型言語ではポピュラーな機能である、代数的データ型を愛してやまない人は多いだろう。しかし悲しいことに、CoffeeScriptは、代数的データ型を持っていない。今回は、オブジェクトを使って代数的データの条件分岐を表現する方法について書こうと思う。

次のコードは、数式の文字列化や評価を行うプログラムだ。

# 分岐関数
match = (x, c) -> x.apply(c)

# 構築子
num = (x) -> ->@num(x)
add = (x, y) -> ->@add(x, y)
mul = (x, y) -> ->@mul(x, y)

# 数式 1 + 2 * 3
exp = add(num(1), mul(num(2), num(3)))

# 文字列化
p = (exp) -> match exp,
  num: (x) -> String(x)
  add: (x, y) -> "(" + p(x) + ")+(" + p(y) + ")"
  mul: (x, y) -> "(" + p(x) + ")*(" + p(y) + ")"

# 評価
ev = (exp) -> match exp,
  num: (x) -> x
  add: (x, y) -> ev(x) + ev(y)
  mul: (x, y) -> ev(x) * ev(y)

console.log p(exp) + " = " + String(ev(exp))

実行すると、次のように表示される。

(1)+((2)*(3)) = 7

構築子

構築子は、次の形をしている。

c = (x...) -> ->@c(x...)

(仮引数列) -> 式は関数を作る構文、-> 式は引数のない関数をつくる構文で、@メソッド名(実引数列)JavaScriptthisメソッドを呼び出す。つまり構築子cは、this.cx...を引数として呼ぶクロージャーを返す関数だ。

分岐関数

条件分岐は関数matchに、代数的データと、分岐ごとの処理を表すオブジェクトを渡して行う。

分岐関数内では、渡された代数的データ(実体はクロージャー)をオブジェクトに適用している。

match = (x, c) -> x.apply(c)

こうすると、クロージャーがオブジェクトの適切なメソッドを呼び出し、条件分岐を行ってくれる。

もうひとつの方法

別の方法として、次のような分岐関数と構築子を使う方法が考えられる。

# 分岐関数
match = (x, c) -> c[x[0]].apply(c, x[1..])

# 数式の構築子
num = (x) -> ["num", x]
add = (x, y) -> ["add", x, y]
mul = (x, y) -> ["mul", x, y]