継続のはなし
Schemeとかを勉強していると、よく登場する概念に”継続”というものがあります。この記事では、継続とは何か?を平易に説明し、継続への導入あるいは入門を目指すことを目的とします。
継続とは何か?
wikipediaには次のように定義されています。
継続とは、プログラムの実行においてある時点において評価されていない残りのプログラムを意味するものであり、手続きとして表現されるものである。
具体的にどういうことなのか、説明して行きます。
今回はGaucheを使っています。
まずはListの生成を考えます。
gosh> (list (+ 1 2) (+ 3 5) (+ 7 11)) ; (list 3 8 18) (3 8 18)
このときSchemeでは、左から順に引数を評価してから手続きの評価に入ります(ちょうど上記のコメントのように)。
例えば,(+ 3 5)を評価するときは、『3と(+ 3 5)を評価した後に、(+ 7 11)を評価し、Listを生成する』のように処理系は解釈しています。
この、『3と(+ 3 5)を評価した後に、(+ 7 11)を評価し、Listを生成する』のことを”継続”といいます。
別の視点から考えてみます。もう一度”継続”の定義を見て下さい。
継続とは、プログラムの実行においてある時点において評価されていない残りのプログラムを意味するものであり、手続きとして表現されるものである。
これに従い、手続きとして表現してみます。
gosh> (define cont (lambda (x) (list 3 x (+ 7 11)))) gosh> (cont (+ 3 5)) ; = (list (+ 1 2) (+ 3 5) (+ 7 11)) (3 8 18)
手続きcontが継続、『3とx=(+ 3 5)を評価した後に、(+ 7 11)を評価し、Listを生成する』を表現しています。
継続を扱う
Schemeでは、継続を扱うための手続きが容易されています。
call-with-current-continuation手続きは、慣習的に、ccやcallのようにdefineします。
gosh> (define cc call-with-current-continuation) ;もしくは (define call call-with-current-continuation)
例 test.scm
(define cc call-with-current-continuation) (define x 100) (define z 700) (define cont '()) (display (list x (cc (lambda (c) (set! cont c) 300)) z)) (newline) ; (100 300 700) ; このときcontは『100と引数を評価した後に、zを評価し、Listを生成する』という継続である。 (display (cont 250)) (newline) ; (100 250 700) (define x 0.1) (define z 0.7) (display (cont 0.3)) (newline) ; (100 0.3 700)
ここで注意して欲しいのは,継続contは、『listの第一引数は評価し終えており、第三引数はこれからzを評価する』ということである。
継続には、もうひとつの側面がある。 例 test.scmの続き
; test.scmが記述されているとする。 (display (cons (cont 1) 'something)) (newline) ; (100 1 0.7) (display (cons '(100 1 0.7) 'something)) (newline) ; ((100 1 0.7) . something)
なぜ、このような結果になるのか。継続contは、『100と引数を評価した後に、zを評価し、Listを生成する』ことを表現している。一方で、consを呼び出し、第一引数を評価するときの継続は、『引数を評価し、第二引数を評価しconsを行う』である。
つまり、継続contを呼び出すということは、本来の継続『引数を評価し、第二引数を評価しconsを行う』の代わりに,『100と引数を評価した後に、zを評価し、Listを生成する』を行うことを意味する.
結果的に、新たな継続を呼び出すと、本来の継続を直ちに終了し、新たな継続を実行する。このように、Schemeでは継続を明示的に扱うことができる。
継続を活用する
例外処理
Listの要素の積を計算する。 product.scm
(define cc call-with-current-continuation) (define (product x) (cc (lambda (cont) (letrec ((iter (lambda (x sum) (cond ((null? x) sum) ((not (number? (car x))) (cont (cons "Error : detect not a number" (car x)))) (#t (iter (cdr x) (* sum (car x)))))))) (iter x 1))) )) (display (product '(1 2 3 4 5))) (newline) ; 120 (display (product '(1 2 3 a 5))) (newline) ; ("Error : detect not a number" . a)
File IO
ファイルに文字列を書き込み、ファイルの文字を数え上げる。fileio.scm
(define (count-chars fname) (call-with-input-file fname (lambda (in) (let loop ((c (read-char in)) (count 0)) (cond ((eof-object? c) count) (else (loop (read-char in) (+ count 1)))))))) (define (write-message fname) (call-with-output-file fname (lambda (out) (format out "I'm a Schemer") (newline out) (flush out)))) (define fname "./test.txt") (write-message fname) (display (count-chars fname)) (newline) ;14