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...)
(仮引数列) -> 式
は関数を作る構文、-> 式
は引数のない関数をつくる構文で、@メソッド名(実引数列)
はJavaScriptのthis
のメソッドを呼び出す。つまり構築子c
は、this.c
をx...
を引数として呼ぶクロージャーを返す関数だ。
分岐関数
条件分岐は関数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]