12行で書ける継続付きインタプリタ

どうも、継続がよくわからないので、とても短いサンプルを作ってみました。

["mul", 3, ["add", 1, 4]]

のようなjsonデータを読み込み、実行すると15を返すような
インタプリタを作ると以下のようになります。

function eval(s){
	if (s instanceof Array) {
		switch(s[0]){
		case "add": return eval(s[1]) + eval(s[2]);
		case "mul": return eval(s[1]) * eval(s[2]);
		}
	}
	return s;
}

ここで、このプログラムを継続渡しスタイル(CPS)に書き換えて
継続を実装してみました。

function eval(s,k){
	if (k == null) k = function (u){return u}
	if (s instanceof Array) {
		switch(s[0]) {
		case "add": return eval(s[1],function(u){return eval(s[2],function(v){return k(u+v)})})
		case "mul": return eval(s[1],function(u){return eval(s[2],function(v){return k(u*v)})})
		case "cont": return ["#cont",s[1], k]
		case "#cont": return eval(s[1],s[2])
		}
	}
	return k(s);
}

12行です。


処理の続き(継続)は全て関数に突っ込んで次の関数に渡しています。
contがあった場合はそれを#contと言う名前で配列に入れて返しています。
#contの場合は配列の中身を実行するだけです。

Array.prototype.toString = function(){return"["+this.join(",")+"]"}
function log(x){document.write(x+"<br>")}
log(c = eval(["add",["cont",["mul",4,5]],10]))
log(d = eval(["add",["cont",["mul",4,2]],5]))
log(eval(c))
log(eval(d))
log(eval(c))
log(eval(d))

のようにして実行すると、

[#cont,[mul,4,5],function(u){return eval(s[2],function(v){return k(u+v)})}]
[#cont,[mul,4,2],function(u){return eval(s[2],function(v){return k(u+v)})}]
30
13
30
13

と出ます。最初の2つで継続を作成して、後の4行で交互に継続を呼び出しています。