はじめに
こちらはアドベントカレンダー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