コルーチン

プログラミング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)の箇所も同様。こいつらに引数を渡してやれば、今まさに中断されようとしているコルーチンの計算結果を、再開されようとしている別のコルーチンに渡すことができるのかな。そうすればまさに協調動作って感じだな。

自分で戻り先を指定するあたり、かなり不安感を伴う。下手するとあっさり無限ループとかしそうだなあ。