この例だと 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 の開発者な人はチェックをお願いします。
最近のコメント