🔄
ブロック付きメソッドにシンボルを渡す
はじめに
["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