トップ «前の日記(2007-06-14) 最新 次の日記(2007-06-17)» 編集

じじぃの日記、ツッコミ可

Twitter: @jijixi_org
Xbox Live: jijixi

初心者が書いた OCaml 入門
Spotlight tips サイト内リンク集
1970|01|02|
2003|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|02|03|04|05|06|07|08|11|
2011|05|
2012|01|

2007-06-15 [長年日記]

% [Ruby] 『『クロージャによる超軽量並行プロセス』を Ruby で』をもう少し Ruby っぽく

昨日の続き。

% cat picalc2.rb
class Chan
   def initialize
      @receive = false
      @val = []
   end

   def <<(x)
      unless @receive
         @val << x
      else
         if @val.empty?
            @receive = !@receive
            @val = [x]
         else
            f, *rs = @val
            @val = rs
            f.call(x)
         end
      end
      self
   end
   alias :send :<<

   def >>(f)
      if @receive
         @val << f
      else
         if @val.empty?
            @receive = !@receive
            @val = [f]
         else
            x, *ss = @val
            @val = ss
            f.call(x)
         end
      end
      self
   end

   def recv(&f)
      self >> f
   end
end

ほんとは @receive って変数名を、もっと別のにしたかったんだけど、ちょっと良いのが思い付かんかった。 あとはこんな感じで。

irb(main):001:0> require 'picalc2'
=> true
irb(main):002:0> x = Chan.new
=> #<Chan:0x42090 @val=[], @receive=false>
irb(main):003:0> x << 3
=> #<Chan:0x42090 @val=[3], @receive=false>
irb(main):004:0> x >> method(:p)
3
=> #<Chan:0x42090 @val=[], @receive=false>
irb(main):005:0> x << 5 >> lambda do |y| p y end
5
=> #<Chan:0x42090 @val=[], @receive=false>
irb(main):006:0> c = Chan.new
=> #<Chan:0x5abcc @val=[], @receive=false>
irb(main):007:0> repeat = proc { c >> proc {|i| p i; repeat.call} }
=> #<Proc:0x000434e0@(irb):7>
irb(main):008:0> repeat.call
=> #<Chan:0x5abcc @val=[#<Proc:0x00043558@(irb):7>], @receive=true>
irb(main):009:0> c << 1
1
=> #<Chan:0x5abcc @val=[#<Proc:0x00043558@(irb):7>], @receive=true>
irb(main):010:0> c << 2 << 3
2
3
=> #<Chan:0x5abcc @val=[#<Proc:0x00043558@(irb):7>], @receive=true>

あとはまあ、repeat のとことかが末尾呼び出しで終わってるのをジャンプに書き換えたかったんだけど、考えるのめんどくさなったので止めた。 つーか、上記のように書いてても、ちっとも「スタック溢れたらどうしよう」とか思わない (つまり当たり前のように末尾呼び出しの最適化を期待している) のは、ちょっとヤバいかもと思った(苦笑

(追記) ん、待てよ? repeat.call で処理がブロックしてるわけじゃないから、別にスタックは溢れないか。 単にチャンネルの @val に Proc オブジェクトが積まれていくだけだもんな。

% [Ruby][雑談] きむらさんが丸投げを受けてくれたようだが…

なんかちょっと思ってたのと違う気が。 もっとこう、1.8 の挙動がまとめてあって、これはこういう理由だったんだけど、1.9 では云々…みたいな話があったような気がするんだけど、メールじゃなく誰かの日記とかだったかなあ。

ところで全然関係無い話。 今、ふと思って試してみたら、Method クラスには yield メソッドが無いみたいなんだけど、良いのかなこれで。

% irb19
irb(main):001:0> method(:p).yield(1)
NoMethodError: undefined method `yield' for #<Method: Object(Kernel)#p>
        from (irb):1:in `method_missing'
        from (irb):1
        from /usr/local/lib/ruby/1.9/irb.rb:150:in `block (2 levels) in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:259:in `signal_status'
        from /usr/local/lib/ruby/1.9/irb.rb:147:in `block in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:146:in `eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:70:in `block in start'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `catch'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `start'
        from /usr/local/bin/irb19:13:in `<main>'

% [Ruby][雑談] ブロック無しの Proc.new, proc, lambda

せっかくなのでこの辺も、わかる範囲で。

ブロック無しの Proc.new, proc, lambda は、それが使われてるメソッドが持っているブロックを暗黙のブロックとして Proc オブジェクトを作るものです。 で、proc と lambda で警告が出るのは非推奨だから (要するに、それをやるなら Proc.new 使いましょうということです)。

&block という仮引数は、ブロックを Proc オブジェクトにして block という変数に束縛する、というのと同義なので、

def a(&b)
  b.call
end

は、

def a
  b = Proc.new
  b.call
end

と同じような意味です。 で、ここからは個人的な趣味の話。

上の例からもわかるように、ブロックに名前を付ける必要が無い場合は、ブロックを実行するなら yield、Proc にして何かするなら Proc.new を使えば良いので、仮引数すら必要無いわけだけど、これがもう、なんつーか個人的には我慢ならん。 だって、それじゃ def の行だけ見ても、それがブロックを受け取るメソッドなのか、そうでないのか判断できないじゃん。 &block って仮引数があれば、「ああ、これはブロックを受け取るメソッドなんだな」って一目で判る。

そんなわけで、わしは yield も引数無しの Proc.new も大嫌いなので、ブロック使うメソッド書くときは必ず、仮引数で受け取って、そいつに call メソッドを使うようにしてるのであった。

あー、でも、パフォーマンスのことを考えると、ブロックをそのまま実行するだけなら仮引数無しで yield した方が良いんだっけ? それとも、その辺はうまいこと最適化してくれてるんかな。

% [雑談][Ruby][JoCaml] ちょっと知ったかぶりしてみよう

なんかトラックバック来てたんだけど、

ちゃんとプロセスを並行に動作させるにはどうしたらいいんでしょうか。スレッドプール?

ここで言われてる『並行』がどの程度のものをイメージしてるかはともかく、例のアレも一応ちゃんと並行 (concurrent) に動いていると言えると思う。 ただ、コンテキストスイッチが起きるのが『関数が終わったとき』だけなんで、粒度が粗いってだけで。

あとはまあ、あるプロセスが無限ループしたりして戻ってこない場合、全体がそこでストップしてしまうのを何とかしたいとか、並列 (parallel) プロセス (CPU が複数あるときに、本当に複数同時に動くようなプロセス) を作りたいとか、って話になると、通常の言語の機能だけでは辛くなってくるはず。 とりあえず Ruby の場合なら言語自体がスレッドの機能を持ってるので、それを利用してスケジューラを構築すればできるようになると思う (並列プロセスは利用するスレッドが並列駆動に対応してないと無理だけど)。 あとはスレッドプールなり何なりを作って、そこに各プロセスを割り振ってやる。 ちなみに、JoCaml は正にそういうことをやってるはず。

じゃあ、言語自体がスレッドの機能を持っていない C とかは、どうやって pthread みたいなスレッドライブラリを作ってんの?というと、たぶんハードウェアのタイマー割り込みを使ってると思う (確かめないで書いてるけど)。 結局、非同期プロセス (スレッド) とか、そいつらのタイムスケジューリングを実装するためには、

  • 通常の処理の流れとは独立して動くタイマー (と割り込み)
  • タイマー割り込みに対応して、何か処理をするためのハンドラ

くらいが最低限必要なんじゃないかな。 スレッドライブラリを持っている言語なら、この二つは作ることができるはず。

……ほんとか?

本日のツッコミ(全1件) [ツッコミを入れる]
% TrackBack (2007-06-15 17:58)

http://d.hatena.ne.jp/ytakamiya/20070615#1181897876<br>たかみやの日記<br> 『『『クロージャによる超軽量並行プロセス』を Ruby で』をもう少し Ruby っぽく』をちょっとだけ見やすく<br> けっこうどうでもいいんですが、Chan の inspect を次のように再定義しました。 class Chan ... def inspect if @receive return &#34;Channel: Receivers#{ @val.inspect }&#34; else return &#34;Channel: Senders#{ @val.inspect }&#34; end end end 実行するとこんなか

お名前:
E-mail:
コメント:

トップ «前の日記(2007-06-14) 最新 次の日記(2007-06-17)» 編集

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com