継続とはなにか

元々は上のトピックで補足として書いていたものが、長くなって読みづらくなって来たので分けることにした(きっとこれはこれで役に立つこともあるんじゃないかなあという期待も込めて)。書いてる本人も良く分かってないことを調べつつ書いているので、内容の正確さは保証できないことをあらかじめお断りしておく。

schemeなんかで使われる継続は、次のような特徴がある。

  • 「このあとしなければならないこと」をパッケージ化したもの
  • 主に大域脱出に使われる(Cのsetjmp-longjmpとは非常に近い関係があるが、スタックが深くなる方向にもジャンプできるところが根本的に違う)
  • どんな言語のどんな処理にも存在している「処理の流れ」を明示的に扱えるようにしたもの
  • 末尾再帰がgotoと等価であるように、継続はreturnと等価になる(継続はreturnの一般化)
  • call-returnモデルでは、呼んだ関数が戻って来る場所はひとつだが、継続渡しの場合戻り先(飛び先)を自由に切り替えることができる
  • call/ccの引数として継続が渡される。継続を呼び出すと、call/ccの後から実行が再開される。継続を呼び出すときの引数がcall/cc自体の値となる
  • ファーストクラスオブジェクトである(変数に保存できる)
  • 保存した継続は、その後何度でも使える。たとえ継続を生成したブロックを抜けた後でも!
  • 謂わば、既に終了したスタックフレームを黄泉の国から引きずり出してスタックにぶちこむようなもの

call/cc(正式名称call-with-current-continuation)は、次のように説明されている。

現在の継続を手続き (継続手続き) にパッケージ化して、それを引数として procを呼び出します。procが戻ったら、その返り値がcall/ccの値となります。作成された継続手続きがどこかで0個または複数個の引数を伴って呼ばれたら、あたかもcall/ccから戻ったかのように実行が継続されます。その場合、 call/ccは、継続手続きに与えられた引数を複数の値として返します。

はっきり言って訳が分からない。初めてこれを読んですんなりと理解できる人は多分いないだろう。というか、自分が初めて継続の説明を読んだとき、まるでクラインの壷*1みたいな奇妙な感覚を受けた。あるいは服の首のところから手を突っ込んで裾を掴んで裏返すような。

継続の例

(define (f)
    (call/cc (lambda (cont)
        (begin (display 1) (newline)
               (display 2) (newline)
               (cont 10)
               (display 3) (newline))))

この例では、(f)を実行すると(cont 10)の呼び出しでcall/ccの外に大域脱出する。実行結果がどうなるかというと、1, 2の順に出力され、(f)の値として10が返る。3は出力されない。

別の例

(define cont #f)
==> cont
(+ 5 (call/cc (lambda (c)
        (begin (set! cont c)
               10))))
==> 15
(cont 1)
==> 6

この例ではcall/ccの中では渡された継続は使われていない。そのため(call/cc ...)の戻り値は継続と関係なく、最後の値10となり、外側の計算が続く。ここでおもむろに保存された継続を呼び出すと、あたかも(call/cc ...)が1を返したかのようにして、もう一度計算の続きが行われ、6という答えが出る。

参考URL

*1:メビウスの輪の3次元版