なぜRubyでブロック付き引数を持つメソッドの引数として&:upcaseみたいな値を渡せるのか

%w(Ganbaru Watashi Kakkoii).map(&:upcase) #=> ['GANBARU', 'WATASHI', 'KAKKOII']といった、ブロック引数の代わりに&:upcaseを引数に設定するイディオムというのはRubyでよく使われる訳ですが、なぜこれで動くのか?と問われると、スッキリ説明できる人は少ないかも知れません。

手続きオブジェクトを引数として渡すために「&」をつける

Rubyのドキュメントを読みながら理解を進めましょう。ブロック付きメソッド呼び出しという文章でこの仕様について触れられています。

まず、これが普通にブロック引数を渡したときの例です。

%w(Ganbaru Watashi Kakkoii).map {|str| str.upcase } #=> ['GANBARU', 'WATASHI', 'KAKKOII']

ブロック引数を求めるようなメソッドは、ブロック引数の代わりに手続きオブジェクト(Procオブジェクト)を渡すこともできます。手続きオブジェクトを渡すようにしたのが、次の例です。

upcasable = ->(str) { str.upcase }
%w(Ganbaru Watashi Kakkoii).map(&upcasable) #=> ['GANBARU', 'WATASHI', 'KAKKOII']

更に「to_proc」オブジェクトを持つオブジェクトも、&で修飾することによって引数に取ることができると書いてあります。下記のとおりです。

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

つまり、以下のような書き方をすることもできます。

class Foo
  def to_proc
    ->(str) { str.upcase }
  end
end

%w(Ganbaru Watashi Kakkoii).map(&Foo.new) #=> ['GANBARU', 'WATASHI', 'KAKKOII']

ところで、:hogeのようなオブジェクトのクラスであるSymbolクラスにもto_procメソッドがあります。以下のような動作をします。

> :upcase.to_proc
=> #
> :upcase.to_proc.call('ggg')
=> "GGG"

こんな感じの手続きオブジェクトを生成していると思うと分かりやすいです。

->(obj) { obj.send(:upcase) } # :upcase.to_proc

おっと、これは本文の前半で挙げた以下のコード例と、ほとんど同じコードですね!

upcasable = ->(str) { str.upcase }

話は戻って、ブロック付き引数を持つメソッドは、ブロック付き引数の代わりに手続きオブジェクトを渡すことができるのでした。ここで「to_proc」メソッドを持つオブジェクトの場合は、&を先頭につけて渡すことができるのでした。Symbolクラスは「to_proc」メソッドを持っています。なので、タイトル通り、以下の書き方をすることができるわけです。

%w(Ganbaru Watashi Kakkoii).map(&:upcase) #=> ['GANBARU', 'WATASHI', 'KAKKOII']

[初心者向け] RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとかで紹介されているようなイディオムがなぜ動くのか、1つずつ追ってみると、Ruby力アップの底上げになるかも知れません。