なんか似たようなタイトルを見たことあるな…まぁいいか。
Perlで+を使うと幸せになれるよ (*´Д`)ノ - (゚∀゚)o彡 sasata299's blog

ブロック処理 ( do...end もしくは {...} ) の例としてはこのような3種類があります。

#1 ブロックの中に直接処理を書く方法
#2 ブロックの中でやらせたい処理の Proc オブジェクトを呼び出す方法
#3 ブロックを使わずに Proc オブジェクトに & を付けて渡してあげる方法

3つとも全く同じ処理です。#3 みたいに書く事も出来るんですね〜。& が Proc オブジェクトをブロックに変換してくれます。

#1
3.times do
  puts 'hogehoge'
end

proc = Proc.new { puts 'hogehoge' }

#2
3.times do
  proc.call
end

#3
3.times(&proc)

#3 は Proc オブジェクトをブロックとして利用する例です。逆にブロックを Proc オブジェクトとして利用することも可能です。ブロックを受取るメソッドの仮引数として & 付きの引数(仮に hoge とします)を渡すと、呼び出されたブロックを表す Proc オブジェクトが hoge に格納されます。こんな感じ。& って凄いですね。

def sample_mb(&hoge)
  hoge.call "hoge", "fuga" 
end

sample_mb {|a, b| p [a, b]} # ["hoge", "fuga"]

で、本題です。最近知ったんですがこの不思議な & は map や select でも同様の使い方が出来るようなのです。最後の2行はやっていることは全く同じなんですが、下の方がすっきりとしていて良い感じじゃないですか?

require 'rubygems'
require 'active_support'

class Sample
  attr_accessor :id

  def initialize
    @id = Time.now.to_i
  end
end

data1 = Sample.new
data2 = Sample.new
data3 = Sample.new
@data = [data1, data2, data3]

p @data.map {|data| data.id} # [1264564410, 1264564410, 1264564410]
p @data.map(&:id)            # [1264564410, 1264564410, 1264564410]

どういうことかというと、

["aaa", "bbbb", "cc"].map(&:length) ・・・(1)

(1) のように書くと、(2) と書いたのと全く同じことになります。

["aaa", "bbbb", "cc"].map {|word| word.length}・・・(2)

先ほどの #3 の例と同じことです。

map メソッドの引数はブロックであることが期待されるので、(1) で渡されている &:length はブロックとして扱われます。 つまり、:length は Proc オブジェクトであることが期待される のです!!#3 をもう一度見てみましょう :-)

そのため、暗黙的に to_proc メソッドが呼ばれるようです。:length はシンボルなので Symbol#to_proc (active_support で定義されている) が呼ばれます。

※ active_support を require する、もしくは Rails 環境、もしくは Ruby 1.8.7 以降( Symbol クラスに to_proc メソッドが追加されたようです)でないとこの書き方はエラーになるので注意

# activesupport-x.x.x/lib/active_support/core_ext/symbol.rb
unless :test.respond_to?(:to_proc)
  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 { |*args| args.shift.__send__(self, *args) }
    end
  end
end

まぁ、一行だけの処理です。args.shift に ["aaa", "bbbb", "cc"] の文字列が一つずつ渡ります。self が :length です。つまりこういう返り値となって (2) と同じ結果になります。

Proc.new { |word| word.__send__(:length) }

これはかっこいいし、便利じゃないですか〜 (*´д`*)
このエントリーをはてなブックマークに追加