ジェネレータのネスト

以前にschemeでジェネレータを実装してみたことがある。(id:SaitoAtsushi:20060806:1154790343) それには誤りがあったので、まず修正したコードを示そう。

(define (make-generator proc)
  (let1 break #f
    (define (return)
      (proc
       (lambda args
         (let/cc cc
           (set! return cc)
           (apply break args)))))
    (lambda ()
      (let/cc cc
        (set! break cc)
        (return)))))

最も単純な利用例としてカウンタを書くとこんな感じになる。

(define count
  (make-generator
   (lambda(yield)
     ((rec (f n) (yield n) (f (+ 1 n))) 0))))

このカウンタを対話環境で使ってみよう。

gosh> (count)
0
gosh> (count)
1
gosh> (count)
2

ちゃんとジェネレータとして機能している。
しかし、JavaScriptやRubyのジェネレータと決定的に違うことがある。JavaScriptやRubyではyieldはあくまでも文であり、最も内側の定義と対応するのに対し、schemeでの上述の実装ではyieldはただの関数である。
ならば、ただの関数ならではの使い方を出来ないかと考えてみた。

(define fizz
  (make-generator
   (lambda(yield-1)
     (let1 g
         (make-generator
          (lambda(yield-2)
            ((rec (f n)
                  (if (zero? (modulo n 3))
                      (yield-1 "Fizz") ;; <- 外側の定義をぬける
                      (yield-2 n))     ;; <- 内側の定義をぬける
                  (f (+ n 1))) 0)))
       (while (g) => var (yield-1 var))))))

3の倍数のときにだけ"Fizz"という文字列を返すカウンタだ。入り組んでいるが、3の倍数のときはネストしたジェネレータ定義の内側から外側の定義を抜ける構造になっている。
これを思いついたときは面白いと思ったのだけれど、複雑にするばかりで抽象化の役に立っていないということに気付いた。
Document ID: 312d7b7947080fcf9e8edf1696c0382d