ブロック付きメソッド

Rubyを初めて知ったのは、大学院M1の頃に先輩から教えてもらったと思うのだけど、そのとき以来、ブロック付きメソッドがよくわからなかった。何がわからなかったかと言うと、

array.each {|x| puts x}

と言う文があった時に、ブロック変数xには、何の値が入るの? と言うところ。
もちろん、直感的には、arrayのそれぞれの要素が入ると言うのはわかる。
でも、どういうメカニズムでarrayのそれぞれの値が入るのかがわからなかった。

結局、当時は、上記のようなブロック文は、foreachのSyntax Sugarだと思い込んでしまって、それ以上は追求しなかった。そして、foreachの置き換えだけでは、通用しないような文がたくさん存在したので、Ruby=="難しいんですけど"と言う等号が成り立ってしまった。

今回、Redmineを導入するにあたって、Rubyに触れる機会があったので、再勉強中。そもそもeachってどうやって実装してんの? と途方に暮れてたら、id:tkuroが手を差し伸べてくれた*1
http://d.hatena.ne.jp/tkuro/20090803/1249299937

とりあえず、Rubyにおいてブロック付きメソッドを実装するためには、二つの方法があるらしい。

callメソッドを使う方法
def each(&block)
  i = 0
  while i < self.size
    block.call(self[i])
    i+=1
  end
end

イメージ的には、無名関数を作って、それをメソッドに引き渡している感じがよくわかる。
このようなやり方は、Perlとかでよくやっていたので、理解しやすい。
そして、ブロック変数の|x|には、callメソッドの引数が入るわけね。

yieldを使う方法
def each()
  i = 0
  while i < self.size
    yield self[i]
    i+=1
  end
end

これは、Perlだと何に該当するんだろう? ぼくのつたない知識だといわゆる継続と言うものだと思うんだけど、あまりなじみがない。確かSICPに出てきてたと思うので、読んでおく。
動作としては、

  1. 呼び出し元から、eachメソッドが呼ばれると、yieldで一時停止する。
  2. yield文はその引数を呼び出し元のブロック構造に渡す。この引数がブロック変数になる。
  3. ブロック構造実行後、yield関数の直後から、実行がリスタートする。
  4. これを配列がnilになるまで(正確には、例外を発生するまで)繰り返す。

と言うことだよね。
なので、これもブロック変数には、yieldの引数が入る。

ようやく、ブロック変数が自分なりに理解できたよ。
これで、ようやくRubyのスタートポイントに立てたような気がする。手を差し伸べてくれた、みなさんに感謝感謝です。

*1:こういうちょっとした突っ込みは本当にありがたい