はじめに
こちらはアドベントカレンダー17日目の記事です。
本記事では、Rubyでよく見かける「&:メソッド名」という記法とyield
の組み合わせについて解説します。
結論
ブロック付きメソッド内部でyield
を引数を渡して実行すると、第一引数をレシーバとして「&:メソッド名」のメソッドを実行する。
「&」とは?
RubyではProcオブジェクトをあらかじめ作っておき、後からブロック付きメソッドに渡すことができます。
Procオブジェクトをブロック付きメソッドに渡すには「&」をProcオブジェクトの先頭につける必要があります。
method(arg1, arg2, ..., &proc_object)
うーん、じゃあよく見るこれ何だ? proc_object
じゃないやん!と突っ込みたくなります。
irb(main):001> array = ["hoge", "fuga", "Bar"]
=> ["hoge", "fuga", "Bar"]
irb(main):002> array.map(&:upcase)
=> ["HOGE", "FUGA", "BAR"]
実は、to_proc
メソッドを持つオブジェクトであれば「&」を先頭につけてブロック引数として利用することができます。:upcase
はSymbolオブジェクトです。Symbolクラスにはto_proc
メソッドが定義されているのでその条件を満たします。
irb(main):001> :upcase.class
=> Symbol
irb(main):002> :upcase.class.method_defined?(:upcase)
=> true
to_proc
メソッドはメソッド呼び出し時に実行されます。
:メソッド名 と to_proc
ほう。Symbolオブジェクトがto_proc
でProcオブジェクトになるならcallしたらどうなる?と思い実験。
irb(main):001> proc_object = :upcase.to_proc
=> #<Proc:0x0000000106a51ae8(&:upcase) (lambda)>
irb(main):002> proc_object.call
(irb):2:in `<main>': no receiver given (ArgumentError)
レシーバが与えられてないと怒られてしまいましたね😇
実は、Symbolクラスのto_proc
によって生成されるProcオブジェクトはProc#call
の第一引数をレシーバとして「:メソッド名」の「メソッド名」を実行します。
以下の例では、"Fuga"
をレシーバとしてupcase
メソッドが実行されていることがわかります。
irb(main):003> proc_object.call("Fuga")
=> "FUGA"
yieldについて
yield
はブロック付きメソッド内でブロックを呼び出す際に利用します。yield
に引数を渡すと、その値はメソッドに渡されたブロックの引数としてブロック実行時に利用されます。
これを応用すると、Proc#call
のレシーバをメソッド内で指定してブロックの処理を実行することができます。
irb(main):001* def fuga
irb(main):002* yield("fuga")
irb(main):003> end
=> :fuga
irb(main):004> fuga(&:upcase)
=> "FUGA"
mapの実装
ちなみにArray#map
の実装を読むと、配列の要素であるvalue
をyield
の引数として渡しており、これによって配列の各要素がレシーバとなってメソッドが実行されるということになります。
result << yield(value)
参考
https://docs.ruby-lang.org/ja/latest/doc/spec=2fcall.html#block
https://docs.ruby-lang.org/ja/latest/method/Symbol/i/to_proc.html