継続
正しいことと分かりやすい部分は、Gemmaさんのおかげ。間違ってるのと分かりづらい所は俺のせい。
継続でできること
まずは、継続を使うとできることから。
大域脱出
一番簡単なのが大域脱出。
(call/cc (lambda(return) (display "before exit\n") (return 0) (display "never execute this line\n")))
実行結果:
before exit
これは、try/catchと同じ事を継続で実現している。
つまり、継続さえあれば、try/catchは不要になる。
非決定性問題
継続を使うと、こんなステキなamb関数が書ける。
; iは[1,2,3]のうちどれか (define i (amb 1 2 3)) ;; jは[2,5,8]のうちのどれか (define j (amb 3 5 8)) (if (prime? (+ i j)) (cons i j) ; i+jが素数なら、その対を返す (amb)) ; そうでないなら、失敗として扱う。
これで、(2,3)という対を得ることができる。
このamb関数の別名が「天使の演算子」らしい。
継続とは
簡単に言えば、スタックの保存。
スタックマシン
まずは、(+ 1 (* 2 3))という式を考えよう。
これがスタックマシンで実行されるとすると、次のように実行される。
"+"のpush
+ |
"1"のpush
1 |
+ |
"*"のpush
* |
1 |
+ |
"2"のpush
2 |
* |
1 |
+ |
"3"のpush
3 |
2 |
* |
1 |
+ |
")"に達したので先頭2つをpopしてかけ算を計算する。
そして、結果をpushする。
6 |
1 |
+ |
")"に達したので、先頭2つをpopして足し算を計算する。
そして、結果をpushする。
7 |
本当は、スタックマシンなら逆ポーランド記法を使うのが分かりやすい。
でも、今後の説明の関係上、これで我慢してくださいな。
継続
次に、
(define s #f) (display (+ 1 (call/cc (lambda(cc) (set! s cc) 2)))) (display #\Newline) (display (s 5)) (display #\Newline) (display (s 6)) (display #\Newline)
について考える。
まず、これを実行すると次のようになる。
3 6 7
(+ 1 (callcc (lambda(cc) ....)))について考える。
(+ 1 ...)まで実行した段階で、スタックは次のような状態になる。
1 |
+ |
そして、ccにはこのスタックが保存される。
より正確に言うと『ccを呼ぶと、このスタックの状態が復元されて、処理が継続される』。
つまり、ccは次の関数と等価になる。
(define (cc x) (+ 1 x))
# だめだ。あんまり分かりやすくない。Gemmaさんの説明は分かりやすかったのに・・。