この例だと 10 行目から 2 行目に戻るべきだと思うんだけど、RhinoWithContinuations には 7 行目に戻ると書いてある。実行環境がないので試せない。誰か試して。
01 function someFunction() { 02 var kont = new Continuation(); 03 print("captured: " + kont); 04 return kont; 05 } 06 07 var k = someFunction(); 08 if (k instanceof Continuation) { 09 print("k is a continuation"); 10 k(200); 11 } else { 12 print("k is now a " + typeof(k)); 13 } 14 print(k);
Rhino は JVM 上で callcc を実現していると2ちゃんねるの ruby スレに書いてあったんで、ちょっと調べたわけだけど。確かに、Rhino は継続を実装しているけど、これって VM を Java で書いているじゃん。そりゃ継続でもなんでも実装できるだろ。文書中では interpretive mode と呼ばれている。
Rhino には JavaScript のコードを JVM のバイトコードに変換する機能も interpretive mode とは別に搭載されている。
tonakai = nil kobito = nil santa = nil sansangogo = nil r = nil tonakai = Array.new(10){ lambda{ santa.yield(:tonakai) } } kobito = Array.new(3){ lambda{ santa.yield(:kobito) } } santa = Fiber.new do h = Hash.new(0) loop do guest = sansangogo.yield ret = h[guest] += 1 puts "#{guest}, #{ret}" if guest == :kobito and ret == 3 puts "3 Kobitos are reached." break r = :go_kaiging elsif guest == :tonakai and ret == 9 puts "9 Tonakais are reached." break r = :go_delivering end end end sansangogo = Fiber.new do loop do break if not r.nil? or ( tonakai.size == 0 and kobito.size == 0 ) if rand(tonakai.size + kobito.size + 1) < tonakai.size tonakai.pop.yield else kobito.pop.yield end end end santa.yield
Fiber のブロックの中でループを回すことが多いだろうから、Fiber.loop{|v| ...} みたいなのがあったら便利かと思ったけど、そうでもないか。直観的な仕様を思いつかない。
ruby-dev で話題になった Lua semi-coroutine。Fiber の呼び出しがループになれない。以下は禁止。
fbr2 = nil fbr1 = Fiber.new do fbr2.yield end fbr2 = Fiber.new do fbr1.yield end fbr1.yield
リングベンチマーク も禁止。
以下のように実装できる。
$cat g.rb class CoLua < Fiber @@h = {} alias :__yield__ :yield undef :yield def resume(*v) h = @@h[Thread.current] ||= {} raise "can not resume parent fibers" if h[self] begin h[Fiber.current] = true __yield__(*v) ensure h.delete(Fiber.current) end end end fbr3 = nil fbr2 = nil fbr1 = CoLua.new do p 1 fbr2.resume p 3 fbr2.resume end fbr2 = CoLua.new do p 2 CoLua.yield p 4 fbr3.resume end fbr3 = CoLua.new do p 5 fbr1.yield #=> エラー end fbr1.resume
$ ruby-1.9 g.rb 1 2 3 4 5 g.rb:10:in `resume': unhandled exception from g.rb:35:in `<main>'
現在の Fiber の分かりにくいところは、Fiber#yield がいつ戻ってくるかが、Fiber.new に与えられたブロックだけから分からないところだと思う。
fbr = Fiber.new do fiber_called_in_this_method() #=> X other_fiber.yield #=> Y Fiber.yield 1 #=> Z end fbr.yield #=> A この場所にいつ返ってくるか。
現在の ruby-1.9 の実装では
のだけど、Lua の semi-coroutine なら必ず Z から A に戻ってくる。
と思ったけど、 fiber_called_in_this_method() の中で、Fiber.yield が呼ばれたら一緒か。でも、普通 呼ばないから大丈夫。
これでどうだ。
class CoLua < Fiber @@h = {} attr_accessor :parent def resume(*v) @parent = Fiber.current h = @@h[Thread.current] ||= {} raise "can not resume parent fibers" if h[self] begin h[Fiber.current] = true self.yield(*v) ensure h.delete(Fiber.current) end end def self.yield(*v) Fiber.current.parent.yield(*v) end end
ruby-dev:30990 の generator の入れ子の例も動いた。
ささだんが ruby-dev に投げたやつを、ruby-dev のえんどうさんの投稿などを参考に改良。
show Generator.new{|g| g.yield 1 show Generator.new{|g2| g2.yield 2 g.yield 3 g2.yield 4 } g.yield 5 }
みたいな Generator の入れ子に対応するには、Generator#yield で Fiber.current を記憶する必要があると思う
class Generator def initialize enum = nil, &block @finished = false @index = 0 @enum = enum @block = block @parent = Fiber.current @fib = if block_given? Fiber.new{ yield self @finished = true @parent.yield } else Fiber.new{ enum.each{|e| @e = e @parent.yield } @finished = true @parent.yield } end @fib.yield end def next? !@finished end def next raise "No element remained" if @finished ret = @e @index += 1 @parent = Fiber.current @fib.yield ret end def end? @finished end def yield value @e = value @fib = Fiber.current @parent.yield end def current @e end def index @index end alias pos index def rewind initialize(@enum, &@block) if @index.nonzero? self end end def show g while g.next? puts "#{Fiber.current}, #{g.current}" g.next end end show Generator.new{|g| g.yield 1 show Generator.new{|g2| g2.yield 2 g.yield 3 g2.yield 4 } g.yield 5 } gg = Generator.new{|g| g.yield :a g.yield :b g.yield :c } Fiber.new do show gg end.yield
ブロックの引数を必要としなくなる。この方式のメリットは、Generator の入れ子などというものが、以下のように、書こうとしても書けなくなるところ。というか、Generator のブロックの引数って、必要ないならそれにこしたことない代物だよね。
show Generator.new{ Generator.yield 1 show Generator.new{ Generator.yield 2 Generator.yield 3 Generator.yield 4 } Generator.yield 5 }
class CoLua < Fiber @@h = {} attr_accessor :parent alias :__yield__ :yield def resume(*v) h = @@h[Thread.current] ||= {} raise "can not resume parent fibers" if h[self] begin @parent = Fiber.current h[Fiber.current] = true self.__yield__(*v) ensure @parent = nil h.delete(Fiber.current) end end def self.yield(*v) CoLua.current.parent.yield(*v) end end class Generator def Generator.yield(*v) CoLua.yield(*v) end def initialize enum = nil, &block @finished = false @index = 0 @enum = enum @block = block @fib = if block_given? CoLua.new{ yield @finished = true CoLua.yield } else CoLua.new{ enum.each{|e| CoLua.yield e } @finished = true CoLua.yield } end @e = @fib.resume end def next? !@finished end def next raise "No element remained" if @finished ret = @e @index += 1 @e = @fib.resume ret end def end? @finished end def current @e end def index @index end alias pos index def rewind initialize(@enum, &@block) if @index.nonzero? self end end def show g while g.next? puts "#{Fiber.current}, #{g.current}" g.next end end show Generator.new{ Generator.yield 1 show Generator.new{ Generator.yield 2 Generator.yield 3 Generator.yield 4 } Generator.yield 5 }
色々書き足した。
Thread#kill! を実行した時、スレッドの ensure 節は実行されないけど
自身がメインスレッドであるか最後のスレッドである場合は、プロセスを Kernel.#exit(0) により終了します。
だから、メインスレッドの ensure 節は実行されてもおかしくない、のか。パズルみたいだ。
ThreadGroup に「ThreadGroup#freeze と ThreadGroup#enclose の違い」を追加。
Comparable に <=> 演算子の説明を追加。
ruby の開発者な人はチェックをお願いします。
最近のコメント