コルーチン3(コルーチンから脱出)
プログラミングGaucheのpp298-303。前回のコルーチンによるREPLは(exit)を与えてGaucheのインタプリタを終わらせるまで無限に繰り返していたけど、今回はコルーチンから脱出してGaucheのREPLのトップレベルに戻ってくるように修正するらしい。
そのために定義を修正する必要があるのは構文define-coroutine。
;; コルーチンから抜けて処理系のREPLのトップレベルに ;; 戻ってこれるように修正したdefine-coroutine (define-syntax define-coroutine (syntax-rules () ;; 前回のdefine-coroutineと同じ部分 ((_ (routine yield) body ...) (define (routine) (call/cc (lambda (return) (define (yield) (call/cc (lambda (cont) (enqueue! *tasks* cont) (return)))) body ...)) ((dequeue! *tasks*)))) ;; 新たに追加したブロック ((_ (routine yield exit) body ...) (define (routine) (call/cc (lambda (escape) ; 処理系のREPLトップレベルへ (call/cc (lambda (return) ; 次のコルーチンへ (define (yield) (call/cc (lambda (cont) (enqueue! *tasks* cont) (return)))) (define (exit) (call/cc (lambda (cont) (enqueue! *tasks* cont) (escape)))) body ...)) ((dequeue! *tasks*))))))))
コメントの「次のコルーチンへ」の行で捕捉した継続returnは、トップレベルへ到達するまでの間に( (dequeue! *tasks*) )という仕事、つまり次のコルーチンを取り出して実行するという仕事があるのに対し、コメント「処理系のREPLトップレベルへ」の行で捕捉した継続escapeは何もせずにトップレベルへ直行するので、コルーチンの無限ループから抜けられるというわけね。
しかし、このマクロdefine-coroutine、
- ローカルな手続きyieldとexitの定義がとても似ている
- 後半部分はローカル手続きexitが挿入され、かつ、継続escapeを捕捉するためのcall/ccの皮が一枚被さっただけ
なので、もうちょっとなんとかならんのかな...。
で、上記のdefine-coroutineを使って、処理系のREPLトップレベルへ脱出するための手続きmanager定義すると
(define-coroutine (manager yield exit) (let lp ((count 1)) (print #`"MANAGER(,count)") (exit) (lp (+ count 1))))
こうなる。もちろんyieldしない。yieldしないんだったら引数のyield書きたくない!だけど無理!仕方ないな。
上のmanagerでトップレベルまで抜けた後に、継続を登録したり、削除したりするための雑多な手続きも作っておくと
(define coroutine-add! (lambda (r) (enqueue! *tasks* r))) (define coroutine-del! (lambda () (dequeue! *tasks*))) (define coroutine-restart! (lambda () ((dequeue! *tasks*)))) (define coroutine-skip! (lambda () (coroutine-add! (coroutine-del!))))
こうなる、と。
これで、前回までのようにあらかじめcoroutine-init!で継続を登録しておかなくても、managerがトップレベルまで連れて行ってくれるので、そこでreaderやらevaluatorやらprinterを定義して、coroutine-add!で登録して、coroutine-restart!でコルーチンを再開して...、みたいなこともできる。
実際やった例は以下。見づらいけど一応書いとく。kahua (Kahua Project) · GitHubの19章のコード(chap19.scm)の通りやってるので、見比べれば何をやっているか分かると思う。「gosh>」が処理系のプロンプト、「coroutine>」がコルーチンreaderのプロンプト。
gosh> (manager) MANAGER(1) #<undef> gosh> (coroutine-restart!) MANAGER(2) #<undef> gosh> (define *val* #f) *val* gosh> reader ; 注 gosh> (coroutine-add! reader) ((#<subr continuation> . #0=(#<closure reader>)) . #0#) gosh> (coroutine-skip!) ((#<closure reader> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-restart!) coroutine> 123 READ(1): 123 MANAGER(3) #<undef> gosh> (coroutine-restart!) coroutine> "HELLO" READ(2): HELLO MANAGER(4) #<undef> gosh> printer ; 注 gosh> (coroutine-skip!) ((#<subr continuation> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-add! printer) ((#<subr continuation> #<subr continuation> . #0=(#<closure printer>)) . #0#) gosh> (coroutine-skip!) ((#<subr continuation> #<closure printer> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-restart!) coroutine> 'hello READ(3): 'hello PRINT(1): 'hello MANAGER(5) #<undef> gosh> evaluator ; 注 gosh> (coroutine-skip!) ((#<subr continuation> #<subr continuation> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-add! evaluator) ((#<subr continuation> #<subr continuation> #<subr continuation> . #0=(#<closure evaluator>)) . #0#) gosh> (coroutine-skip!) ((#<subr continuation> #<subr continuation> #<closure evaluator> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-skip!) ((#<subr continuation> #<closure evaluator> #<subr continuation> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-restart!) coroutine> (+ 1 2 3) READ(4): (+ 1 2 3) EVAL(1): 6 PRINT(2): 6 MANAGER(6) #<undef> gosh> (coroutine-skip!) ((#<subr continuation> #<subr continuation> #<subr continuation> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-skip!) ((#<subr continuation> #<subr continuation> #<subr continuation> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-skip!) ((#<subr continuation> #<subr continuation> #<subr continuation> . #0=(#<subr continuation>)) . #0#) gosh> (coroutine-del!) #<subr continuation> gosh> (coroutine-restart!) coroutine> (* 7 11 13) READ(5): (* 7 11 13) EVAL(2): 1001 PRINT(3): 1001 coroutine> (print "OK") READ(6): (print OK) OK EVAL(3): #<undef> PRINT(4): #<undef> coroutine> (read) (foo bar baz) READ(7): (read) EVAL(4): (foo bar baz) PRINT(5): (foo bar baz) coroutine> (define x 123) READ(8): (define x 123) EVAL(5): x PRINT(6): x coroutine> x READ(9): x EVAL(6): 123 PRINT(7): 123 coroutine> (exit) READ(10): (exit) Process scheme finished
コメント「注」の箇所は、emacsの別のバッファに書いたそれぞれの定義をC-xC-eで実行してる箇所(p26参照)。対話環境でタイポしてしまうといろいろ面倒なので。この実行結果からも分かるように、今現在*tasks*の先頭が何なのかをいちいち意識しつつ、coroutine-skip!でキューの順序を制御する必要があるので、少し面倒くさかった。
19章やっと読み終わった。とりあえず、プログラミングGaucheはこれでいったん中断して、今度はSICPを読もうと思う。SICPで挫折しない程度にSchemeを勉強しておくというのが元々の動機だったし。実用的なプログラミングのための解説(文字列処理とか、オブジェクト指向とか)はほとんど読み飛ばしたので、いつか気が向いたら戻ってこよう。
あと、valvallowさんとこのブログ読んでると、The Seasoned Schemerも気になる。The Little Schemerと同時に買って、絶賛積読中。
ということで、プログラミングGauche良い本だった。
- 作者: Kahuaプロジェクト,川合史朗
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/03/14
- メディア: 大型本
- 購入: 22人 クリック: 713回
- この商品を含むブログ (244件) を見る