(call/cc call/cc) とは?

“L.i.S.P”より (C. Queinnec’s L.i.S.P book and code — https://pages.lip6.fr/Christian.Queinnec/WWW/LiSP.html) 第3章の問題。引数の評価順に依存しそうなのは次の問題と思ったのでその部分はカット。

Exercise 3.1: What is the value of (call/cc call/cc)?

単に (call/cc call/cc) だけだとよくわからないので、 (cons 'car (call/cc call/cc)) について考えます。

gosh> (cons 'car (call/cc call/cc))
(car . #<subr "continuation">)
gosh>

とりあえず動かしてみると、 REPL の表示から、何かしら継続が返されていそうだ、ということは解ります。

まず、ここで (call/cc call/cc) を待ち構えている継続、これをどうにか関数で表現しようとしてみます:

(lambda (x)
  ((lambda (v) (PRINT-THEN-REPL v))
   ((lambda (v) (cons 'car v))
    x)))

REPL の print から次のループへという意味で PRINT-THEN-REPL にしました。

この継続は、 x として値を受け取って、次々と関数へ適用してゆく、 (cons 'car v)(PRINT-THEN-REPL v) これで表示されてプロンプトが出る、というものです。

次に、 (cons 'car (call/cc call/cc)) の評価を順に追ってみます。まず、この (call/cc call/cc) の評価から。

一般に、 (call/cc proc) の評価は、渡された関数 proc を、 (call/cc proc) 自身を待ち構えている継続を引数にして呼び出すことなので、 (proc 継続) が評価されます。

問題の (cons 'car (call/cc call/cc)) での (call/cc call/cc) の評価は、渡された関数 call/cc を、 (call/cc call/cc) 自身を待ち構えている継続(これを継続その一とおく)を引数にして呼び出すので、 (call/cc 継続その一) が評価されます。

すると次は、渡された関数 継続その一 を、 (call/cc 継続その一) 自身を待ち構えている継続(これを継続その二とおく)を引数にして (継続その一 継続その二) が呼び出されます。

ここで、 継続その一 とは (call/cc cal/cc) を待ち構えている継続で、その継続の呼び出しが起こる。(この 継続その一 とは、最初に関数で疑似的に表現しようとした継続のことです。)すなわち、 継続その一 が呼び出されて cons を経て、 PRINT-THEN-REPL が評価されて入力待ちになります。この REPL に表示されている結果の cdr 部の #<subr "continuation"> は、 継続その一 を呼び出した時の引数なので、 継続その二 のことで、プロンプトに表示されているのは (car . 継続その二) なのだということになります。

では、 継続その二 とは?これは (call/cc 継続その一) を待ち構えている継続のことで、すなわちそれは (call/cc call/cc) を待ち構えている継続と同じということになります。( (call/cc call/cc)(call/cc 継続その一) は評価した結果、そのまま末尾呼び出しなので)

というわけで、 (call/cc call/cc) の結果は、自身を待ち構えている継続が返される(返されると表現していいのかな…REPL から入力していたら REPL の P に渡される、ということなんだろうと思う)、になります。

この挙動は、結果の継続を呼び出してみると確認できます。

gosh> (cons 'car (call/cc call/cc))
(car . #<subr "continuation">)
gosh> ((cdr *1) 'cdr)
(car . cdr)
gosh>

さて、冒頭に、 (call/cc call/cc) だけだとよくわからない 、と考えたのはなぜなのかを書いてみます。何が よくわからない と考えたのか、自分の思考を整理できるかなと。

トップレベルから (call/cc call/cc) とだけ打ち込んだ時に、返された継続を呼び出しても単に PRINT-THEN-REPL に値が渡されるわけで、どの時点の PRINT-THEN-REPL なのかをはっきり知覚することができない、ということを指して、よくわからない、と表現していたのだと考えています。

gosh> (call/cc call/cc)
#<subr "continuation">
gosh> (*1 'call/cc)
call/cc
gosh>

(*1 'call/cc) から単に返っているようにも見えてしまう(言い替えると、 (*1 'call/cc) を待ち構えている継続が呼び出されているようにも見えてしまう(、とここまで考えると、継続の呼び出しなので 単に返っている と表現するのは適切ではないのかもしれないぞと気付くと考えられますし))、ということ。