コルーチンによる FizzBuzz の実装

まずコルーチンを実装します。
Scheme は継続(Continuation)を扱えるので、簡単にコルーチンを実装できます。

(use util.queue)

(define *tasks* (make-queue))

(define-syntax define-coroutine
  (syntax-rules ()
    ((_ (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)
                  (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*))))))))

(define (coroutine-init! . rs)
  (set! *tasks* (make-queue))
  (for-each (lambda (r)
              (enqueue! *tasks* r))
            rs))

(define (coroutine-add! r) (enqueue! *tasks* r))
(define (coroutine-del!) (dequeue! *tasks*))
(define (coroutine-restart!) ((dequeue! *tasks*)))
(define (coroutine-skip!) (coroutine-add! (coroutine-del!)))

普段はマクロなんて使わないので、Gauche 本に載ってたソースだったかも知れない。たぶんそうw
コルーチンの解説もあるので、Scheme でコルーチンしたい人はぜひ。

実装したコルーチンを使用して FizzBuzz を実装してみます。

(define (print-fizz-buzz limit)
  (define (devidable? m n) (= (remainder m n) 0))
  (define (fizz? n) (devidable? n 3))
  (define (buzz? n) (devidable? n 5))
  (define (fizz-buzz? n) (and (fizz? n) (buzz? n)))
 
  (define *output* (cons 0 ""))
  (define (request-output! priority output)
    (and (> priority (car *output*))
         (set! *output* (cons priority output))))
  (define (init-request!) (set! *output* (cons 0 "")))
  (define (get-request-output) (cdr *output*))
  
  (define-coroutine (cr-number yield)
    (let lp ((c 0))
      (inc! c)
      (request-output! 10 c)
      (yield)
      (lp c)))

  (define-coroutine (cr-fizz yield)
    (let lp ((c 0))
      (inc! c)
      (and (fizz? c) (request-output! 30 'fizz))
      (yield)
      (lp c)))

  (define-coroutine (cr-buzz yield)
    (let lp ((c 0))
      (inc! c)
      (and (buzz? c) (request-output! 20 'buzz))
      (yield)
      (lp c)))

  (define-coroutine (cr-fizz-buzz yield)
    (let lp ((c 0))
      (inc! c)
      (and (fizz-buzz? c) (request-output! 40 'fizz-buzz))
      (yield)
      (lp c)))

  (define-coroutine (cr-limitter yield exit)
    (let lp ((c 0))
      (inc! c)
      (if (>= c limit)
          (begin (newline) (exit))
          (yield))
      (lp c)))

  (define-coroutine (cr-printer yield)
    (let lp ()
      (display (get-request-output))
      (display " ")
      (init-request!)
      (yield)
      (lp)))

  (coroutine-init! cr-fizz cr-buzz cr-fizz-buzz cr-limitter cr-printer)
  (cr-number))

簡単に解説すると、各コルーチンは並行的に実行するので、各コルーチンは

コルーチン1号「おれ出力する!」
コルーチン2号「おれも出力する!」
コルーチン3号「いや、おれが出力するし!」
コルーチン4号「いや、おれだって!」
コルーチン5号「おれも!おれも!」
コルーチン6号「はいはい、優先度の一番高い奴を出力するから、おまえら喧嘩すんな」
コルーチン7号「(表示回数をチェックして)いい加減おまえら全員出力やめれ!」

みたいな感じになっています。コルーチンは単にそれぞれ動作するだけなので、コルーチン間で協調して動作するためには、アプリケーションとして仕組みを用意しなければなりません。
つまり、出力に優先度を付けることで、出力を調整するコルーチンを用意します。また、表示回数をチェックするコルーチンを用意して、コルーチン全体の動作を停止できるようにします。

実行してみます。

gosh> (print-fizz-buzz 100)
1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizz-buzz 16 17 fizz 19 buzz fizz 22 23 fizz buzz 26 fizz 28 29 fizz-buzz 31 32 fizz 34 buzz fizz 37 38 fizz buzz 41 fizz 43 44 fizz-buzz 46 47 fizz 49 buzz fizz 52 53 fizz buzz 56 fizz 58 59 fizz-buzz 61 62 fizz 64 buzz fizz 67 68 fizz buzz 71 fizz 73 74 fizz-buzz 76 77 fizz 79 buzz fizz 82 83 fizz buzz 86 fizz 88 89 fizz-buzz 91 92 fizz 94 buzz fizz 97 98 fizz 

limit 番目が出ないバグがあります。
以下のようにコルーチン cr-limitter を修正してと。

  (define-coroutine (cr-limitter yield exit)
    (let lp ((c 0))
      (inc! c)
      (if (> c limit)                     ;; <--- イコールを取る
          (begin (newline) (exit))
          (yield))
      (lp c)))

再実行してみます。

gosh> (print-fizz-buzz 100)
1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizz-buzz 16 17 fizz 19 buzz fizz 22 23 fizz buzz 26 fizz 28 29 fizz-buzz 31 32 fizz 34 buzz fizz 37 38 fizz buzz 41 fizz 43 44 fizz-buzz 46 47 fizz 49 buzz fizz 52 53 fizz buzz 56 fizz 58 59 fizz-buzz 61 62 fizz 64 buzz fizz 67 68 fizz buzz 71 fizz 73 74 fizz-buzz 76 77 fizz 79 buzz fizz 82 83 fizz buzz 86 fizz 88 89 fizz-buzz 91 92 fizz 94 buzz fizz 97 98 fizz buzz 

今度は正しい結果が得られました。