まず、大域脱出の例を見てみましょう。 SEND+MORE=MONEY でも、 return"ラベル"を定義して、
(return (list S E N D '+ M O R E '= M O N E Y))
なんてことをやっています。
リストlisと述語手続きpredを取り、lisの各要素に順にpredを適用して、 predが真の値を返したら直ちにその要素を返すような関数findはこう書きます。
(define (find pred lis) (call/cc (lambda (return) (for-each (lambda (elt) (if (pred elt) (return elt))) lis) #f)))
lambda (return) はちょっと奇妙です。 私は脳内で、
(define (find pred lis) (let/cc return (for-each (lambda (elt) (if (pred elt) (return elt))) lis) #f))
と読んでいます。
この継続を、グローバル変数などに保存しておくこともできます。 例えば、継続returnを取り出して、グローバル変数 k に保存してみましょう。
(define k #f) (+ 1 (call/cc (lambda (return) (set! k return) (return 1)))) = 2
return、および k には、あそこに戻る継続が入っています。 それで、
(k 10) = 11 (k 50) = 51
になります。
継続というのは、計算のスタックを取り出したものです。計算のスタックとは、"この後何をするのか"を積み上げたものです。ここのスタックは、
<> 1 +
です。そして、 return = k = (+ 1 <>) です。lambda式で書くと return = k = (lambda (x) (+ 1 x)) です。
実は、計算のスタックは、lambda式で書きなおすことができます。
(print (+ 1 2)) = ((lambda (x) (print x)) ((lambda (x) (+ 1 x)) 2)) (* 3 (+ 1 2)) = ((lambda (x) (* 3 x)) ((lambda (x) (+ 1 x)) 2))
スタックのコピーである継続がなぜlambda計算から自然と生まれたのか、わかる気がします。"この後何をするのか"が継続です。
さきほどの"計算のスタックをlambda式で書き直す"は、CPSと呼ばれる書きかたです。 range 関数を書いてみます。 range 0 9 = (0,...,9)
(define (range a b) (if (> a b) [] (cons a (range (+ a 1) b))))
(print (range 0 2)) のスタックはこうなるでしょう。
[] cons 2 cons 1 cons 0 print
lambda式で書き直すと、
((lambda (x) (print x)) ((lambda (x) (cons 0 x)) ((lambda (x) (cons 1 x)) ((lambda (x) (cons 2 x)) []))))
となります。
継続渡しスタイルは、このlambda (x)流の書きかたを陽にする書きかたです。 range 関数はこうなります。
(define (range a b k) (if (> a b) (k []) (range (+ a 1) b (lambda (x) (k (cons a x))))))
((lambda (x) (print x)) ((lambda (x) (cons 0 x)) ((lambda (x) (cons 1 x)) ((lambda (x) (cons 2 x)) []))))と等しくなることを確かめなさい。
この k に入っているものこそ、継続です。 バトー、忘れないで。あなたがプログラムを書くとき、継続は必ずあなたの側にいる。
例えば、 (let ((i (amb 4 6 7)) (j (amb 5 8 11))) (if (prime? (+ i j)) (list i j) (amb))) ;Value 23: (6 5) のようにすると '(4 6 7) と '(5 8 11) のうちから二つの数の和が素数になる組の1つを返します。
これを理解するのに、自分は3ヶ月かかりました。 ambは、バックトラック演算子です。動きを大雑把に言うと、
(let (i (amb 4 6 7))で、 i に 4 が入ると同時に、 この時点のツヅキ、
"6 7)) (j (amb 5 8 11))) (if (prime? (+ i j)) (list i j) (amb)))"
を取り出して、スタックにpush。
次の行、 (j (amb 5 8 11))で、 j に 5が入ると同時に、 この時点のツヅキ、
"8 11))) (if (prime? (+ i j)) (list i j) (amb)))"
を取り出して、スタックにpush。
(prime? (+ 4 5))は偽。(amb)が動く。amb関数は、引数なしで呼ばれると、スタックをpopして、中身の、ツヅキを実行。
"8 11))) (if (prime? (+ i j)) (list i j) (amb)))"
が実行されて、jに8が入ると同時に、 この時点のツヅキ、
"11))) (if (prime? (+ i j)) (list i j) (amb)))"
を取り出して、スタックにpush...という感じです。
(use srfi-1) ;; stack of cc. (define fail '()) ;; nondeterminsm operator (define (amb li) (if (null? li) ((pop! fail)) (call/cc (lambda (cc) (push! fail (lambda () (cc (amb (cdr li))))) (car li))))) (define (toint li) (fold (lambda (a b) (+ a (* b 10))) 0 li)) (define (solve) (let ((digs (iota 10 0)) (digs1 (iota 9 1))) (let* ((S (amb digs1)) (E (amb (lset-difference = digs (list S)))) (N (amb (lset-difference = digs (list S E)))) (D (amb (lset-difference = digs (list S E N)))) (M (amb (lset-difference = digs1 (list S E N D)))) (O (amb (lset-difference = digs (list S E N D M)))) (R (amb (lset-difference = digs (list S E N D M O)))) (Y (amb (lset-difference = digs (list S E N D M O R))))) (if (= (+ (toint (list S E N D)) (toint (list M O R E))) (toint (list M O N E Y))) (list S E N D '+ M O R E '= M O N E Y) (amb '()))))) (print (call/cc (lambda (cc) ;; initial value for fail (push! fail (lambda () (cc 'no-choise))) (solve))))
Leroyのcall/ccライブラリ http://pauillac.inria.fr/~xleroy/software.html があります。
let compt = let next = let c = ref 0 in (fun () -> c := !c + 1; !c) in register_new_service ~url:["compt"] ~get_params:unit (fun _ () () -> return (html (head (title (pcdata "counter")) []) (body [p [pcdata (string_of_int (next ()))]])))
See this example here