脳ざらし紀行


2007-06-02

_ 原作どおりならいいってもんじゃない

むむ、西浦高校野球部 VS 極悪超人をお望みか。


2007-06-03

_ Rhino の継続

この例だと 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);

2007-06-13

_ Rhino と JVM と callcc

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.loop{|v| ...}

Fiber のブロックの中でループを回すことが多いだろうから、Fiber.loop{|v| ...} みたいなのがあったら便利かと思ったけど、そうでもないか。直観的な仕様を思いつかない。


2007-06-14

_ Lua の semi-coroutine っぽいのを ruby-1.9 で

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>'

_ Lua っぽい semi-coroutine はなかなか良い気がする

現在の 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 の実装では

  • X でメソッドを呼んだら、その中で新たに Fiber#yield が呼ばれて、A に返ってくるかも知れない。
  • Y で Fiber#yield が呼ばれて、さらにその Fiber の中でFiber#yield が呼ばれて、A に返ってくるかも知れない。

のだけど、Lua の semi-coroutine なら必ず Z から A に戻ってくる。

と思ったけど、 fiber_called_in_this_method() の中で、Fiber.yield が呼ばれたら一緒か。でも、普通 呼ばないから大丈夫。

_ と思ったら、Fiber#prev は必ずしも親を指さないのか。

これでどうだ。

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 の入れ子の例も動いた。


2007-06-16

_ Fiber で 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

_ semi-coroutine で generator

ブロックの引数を必要としなくなる。この方式のメリットは、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
}

2007-06-24

_ 2000年問題

 p Time.gm(0) #=> Sat Jan 01 00:00:00 UTC 2000

知らなんだ。

_ Thread

色々書き足した。

Thread#kill! を実行した時、スレッドの ensure 節は実行されないけど

自身がメインスレッドであるか最後のスレッドである場合は、プロセスを Kernel.#exit(0) により終了します。

だから、メインスレッドの ensure 節は実行されてもおかしくない、のか。パズルみたいだ。

_ リファレンスの編集作業

ThreadGroup に「ThreadGroup#freeze と ThreadGroup#enclose の違い」を追加。

Comparable に <=> 演算子の説明を追加。

ruby の開発者な人はチェックをお願いします。


最近のコメント

2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|04|05|
2011|04|
2012|03|07|
2013|01|02|07|
トップ 最新 追記