array.map(&:upcase)
上のコードは「配列の内容を全て大文字に変換する」コードです。
では、この記法がどんな動きをするのか説明できますか?
特にRuby初心者や多言語からRubyに移ってきた人は困惑すると思います。
この記事では、メソッド引数の&:メソッド名
が何なのか、実態として何をやっているのかを解説します。
対象読者
・例文のコードの動きが理解できない人
・ブロックは理解できるけど、&:メソッド名
記法がわからない人
・Ruby初心者~中級者の人
&:メソッド名 とは?
&:メソッド名
を解説するにあたり、最低限の知識としてRubyのブロックとProcを理解しておく必要がありますが、今回は、既にRubyのブロックとProcを理解している前提で解説を進めます。
まずは、&:メソッド名
を使った具体的なコードを見ていきましょう。
["Ruby", "Python", "JavaScript"].map(&:upcase)
=> ["RUBY", "PYTHON", "JAVASCRIPT"]
実行結果を見ると、なんとなーく動きは理解できると思いますが、実際何をやってるのかはブラックボックスですよね。
&:upcase
を分解して1つずつ見ていきましょう。
実は&:upcase
はこれ単体ではなく、&
と:upcase
の合わせ技で構成されています。
& | Proc(手続きオブジェクト)に変換する |
:upcase(:メソッド名) | Rubyのシンボル |
ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。それを実現するのが手続きオブジェクト(Proc)です。それをブロックとして渡すにはブロック付きメソッドの最後の引数として `&’ で修飾した手続きオブジェクトを渡します。
メソッド呼び出し(super・ブロック付き・yield) (Ruby 3.2.0)
引数の「&」について
Rubyではメソッドの引数に&をつけると、引数の値(この場合:upcase)をProc(手続きオブジェクト)として扱おうとし、引数の値(この場合:upcase)に対してto_procメソッドを実行します。
で、to_procメソッドはSymbolクラスにも定義されているため、シンボルに対してto_proc
を実行することができるというわけです。
検証のため、Symbol#to_prop
にデバッグを仕込んで試してみます。
class Symbol
def to_proc
p "to_proc was called!!!"
lambda { |obj, *args| obj.send(self, *args) }
end
end
["Ruby", "Python", "JavaScript"].map(&:upcase)
=> "to_proc was called!!!"
["RUBY", "PYTHON", "JAVASCRIPT"]
確かに、to_proc
が実行されていることが確認できました。
to_proc メソッドを持つオブジェクトならば、`&’ 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェクトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。
メソッド呼び出し(super・ブロック付き・yield) (Ruby 3.2.0)
Symbol#to_procとは?
to_procメソッドは渡されたシンボルを手続きオブジェクトのprocとして返すものです。
proc = :upcase.to_proc
=> #<Proc:0x00007fd5018df468(&:upcase)>
procの引数に文字列を渡して実行してみましょう。
Procオブジェクトはcallメソッドで実行することができます。
proc = :upcase.to_proc
proc.call('Ruby')
=> "RUBY"
文字列`Ruby`
に対して、:upcase
を実行できていることが確認できました。
ここまでをまとめると下のコードとなります。
:upcase.to_proc.call('Ruby')
=> "RUBY"
結論
つまるところ、&:メソッド名
の正体は:メソッド名.to_proc.call
を省略したものが実態となります。
実際に実行結果を比較しても同じ結果となることが確認できますね。
["Ruby", "Python", "JavaScript"].map(&:upcase)
=> ["RUBY", "PYTHON", "JAVASCRIPT"]
# &:upcaseと同じ動き
["Ruby", "Python", "JavaScript"].map do |str|
:upcase.to_proc.call(str)
end
=> ["RUBY", "PYTHON", "JAVASCRIPT"]
注意
前述の通り&:メソッド名
はProcとシンボルの概念であるため、mapメソッドに限った記法ではなく、下のように他のメソッドでも利用することが可能です。
# 偶数のみ抽出
[1,2,3,4,5].select(&:even?)
=> [2, 4]
# 文字数が少ないものから照準で並び替え
["apple", "pear", "fig"].sort_by(&:length)
=> ["fig", "pear", "apple"]
# 文字列を数字に変換後に和を計算
["1","2", "3"].sum(&:to_i)
=> 6
最後に
&:メソッド名
記法を使いこなせると、コンパクトでRubyらしいコードを書くことができますね。
ちなみに&:メソッド名
の記法には名前がないらしい…