継続(Continuation)

なんかゴニョゴニョやってるうちに出来たみたい。

> (+ (call/cc (lambda (x) (x 1) 2)) 3)
==> 4

以下のページなども参考にしつつ、継続スタックを自前で管理することで実現した。継続に対応させるために、プリミティブ関数の実装がとても力業になってしまった。

http://www.jah.ne.jp/~naoyuki/Writings/VScheme3.html

それと、レキシカルスコープは意外に簡単に実装できた。要するにオブジェクトの値を求める時に、評価するときの環境じゃなくて定義した時の環境を使えばいい。定義する場所と評価する場所が異なるのは関数オブジェクト(==lambda式)の中だけだから、結局は関数に定義した時の環境を持たせておくだけでよくて、わずか数行の書き換えで実現できてしまった。

継続に対応するコードの例

参考までに、継続に対応するためのコードの例でも載せてみようかな。

継続に対応する前はこんな感じだったコードが

class SFunctionIf extends SPrimitiveFunction implements SpecialForm {
    public String getName() { return "if"; }
    public SObject apply(SEnvironment env, SObject[] args) throws ESexpEvalError {
        if ((args.length != 2) &&
            (args.length != 3))
            throw new ESexpEvalError("Wrong number of arguments", args);
        SObject sobj = SSymbolNil.getInstance();
        if (args[0].eval(env) instanceof SSymbolNil) {
            if (args.length == 3)
                sobj = args[2].eval(env);
        } else {
            sobj = args[1].eval(env);
        }
        return sobj;
    }
}

継続が入ってくるとこんな風になる

class SFunctionIf extends SPrimitiveFunction implements SpecialForm {
    public String getName() { return "if"; }
    public SObject apply(SEnvironment env, SObject[] args) throws ESexpEvalError, EContinuationSignaled {
        if ((args.length != 2) &&
            (args.length != 3))
            throw new ESexpEvalError("Wrong number of arguments", args);
        SObject sobj = SSymbolNil.getInstance();
        final SEnvironment _env = env;
        final SObject[] _args = args;
        SContinuation cont = new SContinuation() {
                public SObject apply(SObject sobj) throws ESexpEvalError, EContinuationSignaled {
                    SEnvironment env = new SEnvironment(_env);
                    SObject[] args = _args;

                    if (sobj instanceof SSymbolNil) {
                        if (args.length == 3) {
                            sobj = args[2].eval(env);
                        } else {
                            sobj = SSymbolNil.getInstance();
                        }
                    } else {
                        sobj = args[1].eval(env);
                    }
                    return sobj;
                }
            };
        if (args[0] instanceof SCons) {
            SProcess.pushContinuation(cont);
            throw new EContinuationSignaled(args[0].eval(env));
        }
        return cont.apply(args[0].eval(env));
    }
}

こうやって、元は一つの関数だったものを無理矢理一部だけ別のクラスにしている。この場合はクラス一つで済んだけど、関数内で継続が何種類もあるような場合はその数だけクラスを作らないといけないし、再帰していると無名クラスにできないとか、いろいろと制約があって苦労した。