🔄
ブロック付きメソッドにシンボルを渡す
はじめに
["abc", "123"].map(&:reverse)
Ruby ではブロック付きメソッドにシンボルを渡すことができるみたい。
メソッドにシンボルを渡すときの処理が気になり、調べた結果をまとめておく。
動作環境
- Ruby 3.1
ブロック付きメソッドとは
ブロック付きメソッドとは制御構造の抽象化のために用いられるメソッドです。(中略) do ... end または { ... } で囲まれたコードの断片 (ブロックと呼ばれる)を後ろに付けてメソッドを呼び出すと、そのメソッドの内部からブロックを評価できます
メソッド呼び出し(super・ブロック付き・yield) (Ruby 3.1 リファレンスマニュアル)
- メソッドにはブロックを渡せるものがあり、このブロックが付随したメソッドのことをブロック付きメソッドという。
-
ブロックとは
do~endや{~}で囲まれている処理のかたまりを表すもの。 -
eachメソッドやmapメソッドはブロックを受け取ることができるメソッドである。
記述方法
sample.rb
# do end を用いた書き方、ブロックが複数行の時によく使われる
result = ["abc", "123"].map do |text|
text.reverse
end
p result
# {} を用いた書き方、ブロックが1行の時によく使われる
p ["abc", "123"].map {|text| text.reverse}
# シンボルを用いた書き方
p ["abc", "123"].map(&:reverse)
出力
ruby sample.rb
# 上記の3つの書き方は同じ出力になる
["cba", "321"]
["cba", "321"]
["cba", "321"]
- 上 2 つはブロックを渡しているが一番下はシンボル[1]に
&をつけて渡している。
ブロック付きメソッドに渡せるもの
to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。(中略)to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。
メソッド呼び出し(super・ブロック付き・yield) (Ruby 3.1 リファレンスマニュアル)
- つまり、ブロック付きメソッドには
to_procメソッドを持つオブジェクトを渡すことが可能である。 - そして、
Symbolクラスはto_procメソッドを持つので、mapメソッドには&:reverseとして渡すことができる。
Proc オブジェクト
ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。
Proc クラス (Ruby 3.1 リファレンスマニュアル)
-
Procオブジェクトとは簡単に言うと、ブロックをオブジェクト化したものである。
["abc", "123"].map(&:reverse)の処理
-
mapメソッド呼び出し時に、引数であるシンボルオブジェクト:reverseのto_procメソッドが実行(:reverse.to_proc)される。 -
:reverse.to_procを実行すると:reverseのProcオブジェクトが返り値となる。そして、得られたProcオブジェクトをmapメソッドの引数として渡す。 -
mapメソッドは配列の各要素に対して引数であるProcオブジェクトを呼び出す。 -
Procオブジェクトを呼び出す(Proc#call)と、Proc#callの第一引数をレシーバ[2]として、 自身の名前のメソッドを呼び出す。 - つまり、各要素をレシーバとして、
reverseメソッドを呼び出している。
補足
- 私は
:メソッド名とすると、宣言の時点である特定のメソッドのシンボルが作成されると思っていた。 - そのため、別のクラスの同名メソッド(
ArrayクラスのreverseメソッドとStringクラスのreverseメソッド等)をどのように区別しているのかと疑問に思った。 - 調べた結果をまとめておく。
sample.rb
rev = :reverse
p ["abc", "123"].map(&rev)
出力
ruby sample.rb
["cba", "321"]
- 1 行目の
rev = :reverseで、:reverseというシンボルを作成し、変数revにそのシンボルを代入している。この時点では特定のメソッドが直接関連しているわけではない。 - 2 行目の
["abc", "123"].map(&rev)には、&revはrevに代入されたシンボルをProcオブジェクトに変換し、mapメソッドにそのProcオブジェクトを渡している。 - この Proc オブジェクトは各要素(このコードだと
"abc"や"123")から呼び出されたときに
:reverseと同じ名前のメソッド(このコードだとStringクラスのreverseメソッド)を呼び出すものです。 - つまり、
rev自体はシンボルであり、それが&演算子を使ってProcオブジェクトに変換され、そのProcオブジェクトがmapメソッドに渡されている。そして、各要素に対してreverseメソッドが呼び出されることになる。
おわりに
理解が間違っている点などあれば、コメントいただけると幸いです。
最後までお読みいただきありがとうございました。
Discussion