R5RSではdynamic-windという面白い関数が定義されています。

(dynamic-wind before thunk after)

dynamic-windはまずbeforeを呼び出し、その次にthunkを、最後にafterを呼び出します。

試しに以下のようなプログラムを実行してみましょう。

(define *outer-cont* #f)

(let ((message (call/cc (^(cont)
                          (set! *outer-cont* cont)
                          (cont "first")))))
  (print #"out of thunk: message: ~message"))

(define *inner-cont-a* #f)
(define *inner-cont-b* #f)
(define *inner-cont-c* #f)

(define (before)
  (print "this MUST be called before thunk"))

(define (after)
  (print "this MUST be called after thunk"))

(define (thunk)
  (let ((a (call/cc (^(cont)
                      (set! *inner-cont-a* cont)
                      (cont "this is A")))))
    (print #"a: ~a")
    (let ((b (call/cc (^(cont)
                        (set! *inner-cont-b* cont)
                        (cont "this is B")))))
      (print #"b: ~b")
      (let ((c (call/cc (^(cont)
                          (set! *inner-cont-c* cont)
                          (cont "this is C")))))
        (print #"c: ~c")))))

(dynamic-wind before thunk after)

やたらcall/ccしてるのはとりあえず無視してください。現時点では

(let ((message "first")) ...)
(let ((a "this is A")) ...)
(let ((b "this is B")) ...)
(let ((c "this is C")) ...)

してるのと同じです。

とりあえず実行してみましょう。以下のような結果が得られるはずです。

this MUST be called before thunk
a: this is A
b: this is B
c: this is C
this MUST be called after thunk

最初にbeforeが呼ばれ、その後にthunkが呼ばれ、最後にafterが呼ばれています。
ここまでは普通ですね。

しかし、dynamic-windでは、thunkの中に継続を使って入ったり、thunkの中から継続を使って飛び出したりした場合でも、
きちんとbeforeやafterが呼ばれるようになっています。

試しに、先ほど保存した*inner-cont-hoge*を呼び出してみましょう

gosh> (*inner-cont-a* "this is A'")
this MUST be called before thunk
a: this is A'
b: this is B
c: this is C
this MUST be called after thunk

gosh> (*inner-cont-b* "hey! I am B!")
this MUST be called before thunk
b: hey! I am B!
c: this is C
this MUST be called after thunk

gosh> (*inner-cont-c* "hello, my name is C")
this MUST be called before thunk
c: hello, my name is C
this MUST be called after thunk

継続でthunkの中に入る前にbeforeが、thunkでの処理のあとにafterが呼ばれているのが分かります。

また、先ほどのthunkを少し書き換えて、最後に上のほうで保存した*outer-cont*に飛んでみるようにしてみましょう。

(define (thunk)
  (let ((a (call/cc (^(cont)
                      (set! *inner-cont-a* cont)
                      (cont "this is A")))))
    (print #"a: ~a")
    (*outer-cont* "hey! this is second message") ; ここで抜ける
    (let ((b (call/cc (^(cont)
                        (set! *inner-cont-b* cont)
                        (cont "this is B")))))
      (print #"b: ~b")
      (let ((c (call/cc (^(cont)
                          (set! *inner-cont-c* cont)
                          (cont "this is C")))))
        (print #"c: ~c")))))

このプログラムをもう一度実行してみましょう。以下のような結果が得られるはずです。

out of thunk: message: first
this MUST be called before thunk
a: this is A
this MUST be called after thunk
out of thunk: message: hey! this is second message

最後の*outer-cont*の呼び出しでthunkから抜けるときに、afterが呼ばれているのがわかります。

面白いですね。

継続をちゃんと理解したら自分でも実装できるようになるのかな…?