🔄

ブロック付きメソッドにシンボルを渡す

2024/01/24に公開

はじめに

["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として渡すことができる。

https://docs.ruby-lang.org/ja/3.1/class/Symbol.html#I_TO_PROC

Proc オブジェクト

ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。
Proc クラス (Ruby 3.1 リファレンスマニュアル)

  • Procオブジェクトとは簡単に言うと、ブロックをオブジェクト化したものである。

["abc", "123"].map(&:reverse)の処理

  1. mapメソッド呼び出し時に、引数であるシンボルオブジェクト:reverseto_procメソッドが実行(:reverse.to_proc)される。
  2. :reverse.to_procを実行すると:reverseProcオブジェクトが返り値となる。そして、得られたProcオブジェクトをmapメソッドの引数として渡す。
  3. mapメソッドは配列の各要素に対して引数であるProcオブジェクトを呼び出す。
  4. Procオブジェクトを呼び出す(Proc#call)と、Proc#callの第一引数をレシーバ[2]として、 自身の名前のメソッドを呼び出す。
  5. つまり、各要素をレシーバとして、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) には、&revrevに代入されたシンボルをProcオブジェクトに変換し、mapメソッドにそのProcオブジェクトを渡している。
  • この Proc オブジェクトは各要素(このコードだと "abc""123")から呼び出されたときに
    :reverseと同じ名前のメソッド(このコードだと String クラスの reverse メソッド)を呼び出すものです。
  • つまり、rev自体はシンボルであり、それが&演算子を使ってProcオブジェクトに変換され、そのProcオブジェクトがmapメソッドに渡されている。そして、各要素に対して reverse メソッドが呼び出されることになる。

おわりに

理解が間違っている点などあれば、コメントいただけると幸いです。
最後までお読みいただきありがとうございました。

参考

ゼロからわかる Ruby 超入門
https://docs.ruby-lang.org/ja/3.1/doc/spec=2fcall.html#block
https://docs.ruby-lang.org/ja/3.1/class/Proc.html
https://docs.ruby-lang.org/ja/3.1/class/Symbol.html#I_TO_PROC
https://zenn.dev/hayaokimura/articles/ruby-block-reintroduction
https://www.te-nu.com/entry/2022/09/20/180956

脚注
  1. シンボルとは任意の文字列と一対一に対応するオブジェクトです。
    :symbol のようなリテラルで得られる。 ↩︎

  2. レシーバとはメソッドを呼び出されるオブジェクトのことです。 ↩︎

Discussion