Schemeをつくろう(20) read-print-loop
コードリーディングを元に継続なSchemeを作っていこう。
今のところ一個しか継続が無いし、evalも無い。しかし、重要なのはイメージだよ。イメージ。イマジネーションで。
typedef void *(*cont)(scheme); void *print_cont(scheme scm); void *read_cont(scheme scm); object buf; void *print_cont(scheme scm) { display(buf); newline(); return read_cont; } void *read_cont(scheme scm) { buf = read(scm, file_port(stdin)); return print_cont; } void loop(scheme scm, cont c) { for (;;) c = c(scm); }
実行してみると、
(1 2 3 4) (1 2 3 4)
できたぁ!!エコーするだけなんだけど・・・
次にやることを渡せばいいんだ。簡単。
よく考えてみたら、継続ってデザパタのStateパターンに酷似してる。Stateパターンも次にやることを渡す。Stateパターンを使えば処理がグラフみたいになるし、継続を使ってもグラフみたいになる。オブジェクト指向でも継続は使えるってことだ。なんか繋がってきた!!
ぅぅん。出来れば、contを再帰的に定義したいんだけど、
typedef cont (*cont)(scheme);
駄目らしい。構造体にすれば解決出来そうだけど。旨い方法がありそうな気がする。
まぁいいや。contは構造体にする予定なので、このイメージを元に膨らませていこう。
その後
継続を構造体にしてみた。やっぱりStateパターンライクな感じ。Cだと関数ポインタで手軽にState出来るけど、オブジェクト指向だとクラスが山ほど必要になりそう。怖いなぁ。
次の問題は、レジスタをどのように使っていくのかというイメージが全く無い事。またコードリーディングに戻る。
またコードリーディング
わかった。前回のメモで重要なレジスタが抜けてた。
オペレータ + 引数 = 値
値が抜け落ちてた。解決。
アレ?次に評価するときは、値が引数になる。値は1つ。引数はいっぱいあるな。
値からどのように引数を作り出すか。もうちょい読もう。
わかった。evalのフェーズは3個あって、applyに渡される。
末尾まで行ったら引数を逆順にというのが気に入らないな。でも、前の実装はeval_mapとか作って、再帰でやってたから、結局逆順にしてるのと変わらないのか。
うぅん。しばし考える。
へっぽこeval搭載
とりあえずちゃんと継続システムが動いてるのか確認する。
scheme print_(scheme scm); scheme read_(scheme scm); scheme eval_(scheme scm); scheme repl_(scheme scm); scheme print_(scheme scm) { display(scm->value); newline(); return pop(); } scheme read_(scheme scm) { scm->value = read(scm, scm->args); if (scm->value == (object) EOF) return reg(NULL, NULL); else return pop(); } scheme eval_(scheme scm) { if (is_pair(scm->value)) error("まだ。"); else return pop(); } scheme repl_(scheme scm) { scm = cont(scm, repl_, NULL); scm = cont(scm, print_, NULL); scm = cont(scm, eval_, NULL); return reg(read_, file_port(stdin)); } void loop(scheme scm) { while (scm->op != NULL) scm = scm->op(scm); }
contは継続を貯める。popは継続をpopする。regは現在のレジスタにセット。popとregはマクロ。
実行してみると、
Hello Scheme World!! Hello Scheme World!! 123 456 789 123 456 789 (+ 1 2 3) error : まだ。
おぉぉぉぉ。
継続をpopしながら実行できることが確認できた。とりあえずオッケー。
継続はレジスタの状態をすっかり元に戻してしまうので、扱いは相当シビアだ。魔法の道具として扱うには少々危険な香りが漂う。継続のpopは相当注意したほうがいいらしい。
またコードリーディング
evalを読み中。す・凄すぎる。
なんでこれでツリーが解析できるんだ。感動はするけど、どうなってるのかよくわからない。
継続に分解して、一個解析する。解析し終わったら継続を戻す。次のを読む。
継続に分解して、一個解析する。解析し終わったら継続を戻す。次のを読む。
継続に分解して、一個解析する。解析し終わったら継続を戻す。次のを読む・・・
継続を戻すと、値以外のレジスタは全て捨てられる。綺麗さっぱり元の状態に戻るのだ。これによって次のステップに進めるんだけど。こんなんでプログラムが動いてるのが信じられねぇ!!
えぇぇぇ。ナンダコレハ!!なんなんだ!!変態すぎる!!
そして
足し算するぞー。
scm> (+ (+) 1 (+ 2 (+ 3 4) 5) (+ (+ (+ 6) 7) 8 9) (+ 10)) 55
スゲーーーーーーーーーーーーーなんで計算できるんだ(笑)
原理はわかる。原理はわかるが、過去に戻れるという現実をまだ受け入れられない。