バリケンのRuby日記 RSSフィード

2006-12-22

[][] ActiveSupport(2) - Symbol#to_proc  ActiveSupport(2) - Symbol#to_proc - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  ActiveSupport(2) - Symbol#to_proc - バリケンのRuby日記  ActiveSupport(2) - Symbol#to_proc - バリケンのRuby日記 のブックマークコメント

Symbol#to_proc

まずはActiveSupportの中でも[これはすごい][これはひどい]賛否両論Symbol#to_procについて見てみるよ。Symbol#to_procを定義することで、

[1,2,3,4,5,'a','b','c'].map{|i| i.succ }

のようなブロック付きメソッド呼び出しが

[1,2,3,4,5,'a','b','c'].map(&:succ)

のようにブロックパラメータを省略して書けるようになるんだって。

Symbol#to_procは、(activesupport-1.3.1では)次の場所で定義されているよ。

(Rubyインストールパス)lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/core_ext/symbol.rb

じゃあ、ソースコードを見てみよう!

class Symbol
  # Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
  #
  #   # The same as people.collect { |p| p.name }
  #   people.collect(&:name)
  #
  #   # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
  #   people.select(&:manager?).collect(&:salary)
  def to_proc
    Proc.new { |obj, *args| obj.send(self, *args) }
  end
end

ほとんどがコメントで、to_procメソッドの実装はたったの1行だね。

このメソッドは、Symbolクラスインスタンス(つまりSymbolオブジェクト)に対してto_procメソッドが実行されると、「Proc.new { |obj, *args| obj.send(self, *args) }」というProcオブジェクトを返す、という挙動をしているよ。

このメソッドの挙動についてはあとで説明するとして、to_procメソッドってどんなときに呼ばれるの?と思ったら、リファレンスマニュアルの「ブロック付きメソッド呼び出し」のところに書いてあったよ

ruby 1.7 feature: version 1.7 では、to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます(デフォルトProcMethod オブジェクトは共に to_proc メソッドを持ちます)。to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。

Rubyリファレンスマニュアル - メソッド呼び出し

つまり、ブロック付きのメソッドは

  1. ブロックを直接与えると、ブロックがメソッド内で(yieldで)呼ばれる
  2. 「&」修飾した引数がある場合は、そのオブジェクトのto_procメソッドを呼び出し、戻り値として得られたProcオブジェクトがメソッド内で(yieldで)呼ばれる

という動作をするんだね。

ちなみに以前(その1)(その2)(その3)、ブロックProcオブジェクトについてまとめてみたから、参考にしてみてね。

あとはここまで来たら、実際に挙動を追いかけてみたほうが早いよね。じゃあ、配列に対してArray#mapメソッドを実行している例を見てみよう!

$ irb
irb(main):001:0> class Symbol
irb(main):002:1>   def to_proc
irb(main):003:2>     Proc.new { |obj, *args| obj.send(self, *args) }
irb(main):004:2>   end
irb(main):005:1> end
=> nil
irb(main):006:0> [1,2,3,4,5,'a','b','c'].map(&:succ)
=> [2, 3, 4, 5, 6, "b", "c", "d"]
irb(main):007:0> exit

配列の各要素にsuccメソッドが実行されていくのがわかるよね。

どうしてこうなるかというと、まずmap引数に「:succ」というシンボルを「&」をつけて与えているから、さっき引用したリファレンスマニュアルのとおりに:succ.to_procが呼び出されるよ。

次に、:succ.to_procの戻り値は「Proc.new { |obj, *args| obj.send(self, *args) }」だったね。ここでのselfは:succだから、結局

[1,2,3,4,5,'a','b','c'].map{|obj, *args| obj.send(:succ, *args) }

と同じになるんだね。そしてArray#mapブロック引数をひとつしか取らないから、

[1,2,3,4,5,'a','b','c'].map{|obj| obj.send(:succ) }

だね。さらに、Object#sendは「引数で与えられた文字列またはシンボルに対応するメソッドを呼び出す」ので、

[1,2,3,4,5,'a','b','c'].map{|obj| obj.succ }

となるんだね。

というわけで、Symbol#to_procメソッドを定義することで、

[1,2,3,4,5,'a','b','c'].map{|i| i.succ }

のようなブロック付きメソッド呼び出しが

[1,2,3,4,5,'a','b','c'].map(&:succ)

のようにブロックパラメータを省略して書けるようになる、というカラクリみたいだよ。