コルーチン
プログラミングGaucheのpp.294-296の「コルーチン」の前半。
call/ccでコルーチンまで実現できるらしい。ってことで、コルーチンを実現するための構文define-coroutineの定義を写経。
;; キューの機能を使うから (use util.queue) ;; コルーチン再開するときの戻り先としての継続 (define *tasks* (make-queue)) ;; コルーチン定義用の構文 (define-syntax define-coroutine (syntax-rules () ((_ (routine yield) body ...) (define routine (lambda () (call/cc (lambda (return) (define yield (lambda () (call/cc (lambda (cont) (enqueue! *tasks* cont) (return))))) body ...)) ((dequeue! *tasks*))))))) ;; 複数のルーチンを登録する場合の初期設定 (define coroutine-init! (lambda lis (set! *tasks* (make-queue)) (for-each (lambda (r) (enqueue! *tasks* r)) lis))) ;; コルーチン1号 (define-coroutine (three yield) (let loop () (print 'one) (yield) (print 'two) (yield) (print 'three) (yield) (loop))) ;; コルーチン2号 (define-coroutine (五 つぎ) (let るーぷ () (print 'いち) (つぎ) (print 'に) (つぎ) (print 'さん) (つぎ) (print 'し) (つぎ) (print 'ごー!) (つぎ) (るーぷ)))
使ってみるとこんな感じ
gosh> (coroutine-init! 五) #<undef> gosh> (three) one いち two に three さん one し two ごー! three いち . . .
さらっと読んですぐはcoroutine-init!が何のためにあるのか分からなかった。あと、threeを実行したのに、手続き「五」が実行される理由も分からなかった。
上の例ではthreeを実行する前にあらかじめcoroutine-init!で五を登録しておくことで、threeの1回目のyieldでthreeから抜けて五が呼び出されるように仕向けているわけだな。
しかも、yieldでルーチンを中断する直前に*tasks*に継続contを登録しているので、いつか*tasks*からcontがdequeue!されてそれが実行され、そこから処理が再開されることになる。
(dequeue! *tasks*)で取りだした手続きがthreeや五だった場合は問題無いけど、call/ccで捕捉した継続だった場合は引数が1個要るんじゃないのかな?(return)の箇所も同様。こいつらに引数を渡してやれば、今まさに中断されようとしているコルーチンの計算結果を、再開されようとしている別のコルーチンに渡すことができるのかな。そうすればまさに協調動作って感じだな。
自分で戻り先を指定するあたり、かなり不安感を伴う。下手するとあっさり無限ループとかしそうだなあ。