【Ruby】map(&:メソッド名)とは? | &:メソッド名の動きを丁寧に解説

array.map(&:upcase)

上のコードは「配列の内容を全て大文字に変換する」コードです。

では、この記法がどんな動きをするのか説明できますか?

特にRuby初心者や多言語からRubyに移ってきた人は困惑すると思います。

この記事では、メソッド引数の&:メソッド名が何なのか、実態として何をやっているのかを解説します。

対象読者
・例文のコードの動きが理解できない人
・ブロックは理解できるけど、&:メソッド名記法がわからない人
・Ruby初心者~中級者の人

&:メソッド名 とは?

&:メソッド名を解説するにあたり、最低限の知識としてRubyのブロックとProcを理解しておく必要がありますが、今回は、既にRubyのブロックとProcを理解している前提で解説を進めます。

まずは、&:メソッド名を使った具体的なコードを見ていきましょう。

配列内の文字列をすべて大文字(upcase)にする
["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にデバッグを仕込んで試してみます。

Symbol#to_procにデバッグを仕込む
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らしいコードを書くことができますね。

ちなみに&:メソッド名の記法には名前がないらしい…

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です