継続を使ってみたかった

ながい前置き

継続。scheme使う人が一度は使ってみたくなる継続。わかるまでわからない概念だけど、多分使わないとわからないと思う。名前付きletとかもコード書いてみるまでわからなかった。そこで継続を使ってなにか作ってみる。
継続を使って何か作る、といわれるとまっ先にコルーチンが思い浮かぶのだけど、コルーチンを実装するには待ち行列(キュー)やら何やら、継続以外の要素が必要になってくる。それに、多分キューを実装し始めると、
キューとスタックって大体おんなじだよね
→じゃあ抽象化したいね
→dequeを書いて、enqueue、dequeueをかぶせようか
となってだいぶ脱線するに決まっている。
もっと小さいお題で、継続に集中できるのをやろう、というわけで継続を使ってfizzbuzzを書くことにする。

こういうのがやりたい

呼ぶたびに数かFizzかBuzzかを、一つずつ返す引数のない……、コードを見てもらおう。

(define gen (fizzbuzz-gen 1))
(define gen-type2 (fizzbuzz-gen 100))

(display (gen))(newline) ; 1
(display (gen))(newline) ; 2
(display (gen))(newline) ; Fizz

(display (gen-type2))(newline) ; Buzz
(display (gen-type2))(newline) ; 101
(display (gen-type2))(newline) ; Fizz

genやgen-type2は一回呼ぶたびに、genは1から、gen-type2は100から順番に一個ずつfizzbuzz処理(?)をして返す。
繰り返し文を使ってfizzbuzzを書くときは、nからmまで、一つ処理して、カウンタを回す。このやり方では最初から最後まで全部ループの中で処理される。
(fizzbuzz-gen n)が返す関数は、この「一つ処理して」と「カウンタを回す」の間でループを一時停止して、処理の結果を持って呼び出し側に帰ってくる。こういう関数をジェネレータという。らしい。
Pythonだとyieldとかnext()とか使って、Luaだとcoroutine.create()を使って書くんだったか。
「関数を呼んで次の値を一個ずつもらう」という点ではC++でいうコンテナのイテレータが近いか。
ジェネレータをもう一回呼ぶと、カウンタが回って、次の処理を始める。逆に言うと、呼ばない限りループは止まりっぱなし、呼べば呼んだだけ(桁あふれしない限りは)値を返すので、全部ループ内で処理する時のように、処理する範囲の上限を気にしなくていい。ジェネレータを呼べば値は欲しいだけもらえる。こちらがジェネレータを何回呼び出すかが上限を決める。
とまあ、こういうfizzbuzzジェネレータが書けたらいいなあ、と思う。
思ったあたりで今日は時間切れなので続きは次回。