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に渡される。

  1. オペランドをpop
  2. 引数をeval (引数を前から見て行って、valueをpushする。)
  3. 末尾まで行ったら引数を逆順に。
  4. 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

スゲーーーーーーーーーーーーーなんで計算できるんだ(笑)


原理はわかる。原理はわかるが、過去に戻れるという現実をまだ受け入れられない。