call/cc を使ったコルーチンもどきでフィボナッチ数列


ちまたでは、なにやらフィボナッチ数列が流行っているようなのですが、フィボナッチ数列といえば、以前、Lua について触れたときに、資料で見かけたコルーチンを使ったフィボナッチ数列が印象深かったのに、まだそれを SqueakSmalltalk で書いていなかったな…と思い出したのでさっそく書いてみました(Continuations のインストール が必要です)。

| continue suspend resume fibGen next |

suspend := nil.   " for compiler "

resume := [:val |
   [:cont | continue := cont. suspend value: val] callCC].

fibGen := [:aa :bb |
   [resume value: aa. aa := bb flag: (bb := aa + bb)] repeat].

next := [[:sus |
   suspend := sus.
   continue ifNil: [fibGen value: 1 value: 1].
   continue value] callCC].


World findATranscript: nil. Transcript clear.

20 timesRepeat: [Transcript cr; show: next value]


…と言っても、id:sumim:20060308:p1 にちょっと手を加えただけ。Smalltalk では、多重代入ができないのが悔しかったので、#flag: なんぞを使って少々トリッキーなこと(二重代入もどき)もしてみました。よい子はマネをしないでネ。


ついでに、ほぼ同じものを Ruby でも。

continue = suspend = nil

resume = proc {|val|
   callcc {|cont| continue = cont; suspend.call(val)}
}

fib_gen = proc {|inia,inib| 
   a,b = inia,inib; loop {resume.call(a); a,b = b,a+b}
}

next_val = proc {
   callcc {|sus|
      suspend = sus
      unless continue; fib_gen.call(1,1) end
      continue.call
   }
}

20.times {puts next_val.call}


追記
id:rubyco:20060418:fib さんを拝見していて、Ruby でもブロック変数への再代入が可能だと気が付きました。なぜ駄目だと思いこんでいたのでしょうね…。さらに、ブロック変数がテンポラリ変数をオーバーライドするアレな仕様がこの場合はさいわいして、よりシンプルな記述が可能なようです。

continue = suspend = nil

resume = proc {|val|
   callcc {|continue| suspend.call(val)}
}

fib_gen = proc {|a,b| 
   loop {resume.call(a); a,b = b,a+b}
}

next_val = proc {
   callcc {|suspend|
      unless continue; fib_gen.call(1,1) end
      continue.call
   }
}

20.times {puts next_val.call}