はじめに
配列やハッシュに使うメソッド、mapとかinjectなどを全てProcを使って美しく書きたい。そんな思いから、Arrayに#to_proc
を追加してみた。割りと使えると思うんだ。
種類 | 名前 |
---|---|
前提 | Procとは |
本編 | Arrayにto_proc |
Procとは
ProcクラスはRubyのブロックをオブジェクトとして、格納したもの。
なので、メソッドとしてブロックを受け取るものにその代わりとして、Procメソッドを与えて良い。
例えば、数字の配列を全て倍にしたい場合、きっとあなたは
array = [1,2,3,4]
array.map do |num|
num * 2
end
# => [2,4,6,8]
とするだろう。
ここで、mapメソッドに { |num| num * 2 }
のような、ブロックが渡されている。これをオブジェクトにしようではないか。
double_me = Proc.new { |num| num * 2 }
#または
double_me = Proc.new do |num|
num * 2
end
この、ブロックが格納されたオブジェクトをブロックの代わりにメソッドに渡す時は、引数として前に それを示すために&
をつけなければならない。
array = [1,2,3,4]
array.map(&double_me)
#=> [2,4,6,8]
複雑な処理などは、このようにしたほうが美しい。ちなみに、メソッドをProc
オブジェクトにする。to_proc
というメソッドがある。
@kts_h さんの指摘により気付かされたのですが、
to_proc
メソッドがあると言っても。Symbol#to_proc
とMethod#to_proc
があり、気をつけなければならない。
# Symbol#to_procの解釈
class Symbol
def to_proc
# はじめの引数(と読んでいいのか?)はリシーバーとして
# その他の引数を全て、メソッドの引数として渡す
# つまり、:+.to_proc.call(1,2) とすれば
# receiver は 1、 *args は 2 ということになる。
Proc.new { |receiver, *args| receiver.send(self, *args) }
end
end
# Method#to_procの解釈
class Method
def to_proc
# 受け取った全ての引数を、そのままメソッドの引数とする。
# つまり、method(:double_me).to_proc.call(2) とすれば
# *args が 2 となる
# self が method(:double_me) になる。
Proc.new { |*args| self.call(*args) }
end
end
ざっくり2つの違いをまとめると、
Symbol#to_proc
が返す Proc オブジェクトを呼び出す時に、第一引数をレシーバーとして使う。
Method#to_proc
が返す Proc オブジェクトを呼び出す時に、全ての引数を、引数としてメソッドに渡す。
def double_me(num)
num * 2
end
# :double_me.to_proc としてしまうと、Symbol#to_procが呼び出されてしまう。
# 今回の例は、Method#to_procを呼び出したい。
method(:double_me).to_proc
#=> #<Proc:0x007ff9b081ac48(&:dounle_me)>
# つまり、 Proc.new { |num| num * 2 } と同義
さて、これらの知識を使うと、メソッドをブロックの代わりに引数として渡すことが可能な事がわかる。
array = [1,2,3,4]
array.map(&method(:double_me).to_proc)
#=> [2,4,6,8]
# :double_me.to_proc は Proc.new { |num| num * 2 } なことを思い出してほしい。
さらに、引数を渡す時は、to_proc
を省略出来る。
array.map(&method(:double_me))
これで、不可解な記号を使った魔法が出来上がる。
Arrayに#to_proc
をたす
だがしかし、こんなケースでは使えない。
# 名前と敬称を受け取り、挨拶する文を返す。
class Bakabon
def initialize(name)
@name = name
end
def greeting(title)
"こにゃにゃちわー。#{@name} #{title}"
end
end
bakabon = Bakabon.new('バカボン')
rerere = Bakabon.new('レレレのおじさん')
hajime = Bakabon.new('はじめ')
[bakabon, rerere, hajime].map(&:greeting('さん')) # これはできない
そこで、引数を配列として渡せればどうか。
# できれば素敵
[bakabon, rerere, hajime].map(&[:greeting, 'さん']) #だができない。
ならば、出来るようにしましょう。
問題は、Arrayにto_proc
メソッドがないからである。
class Array
def to_proc
# receiver は bakabon, rerere, hajimeになるはず。
# self は [:greeting, 'さん'] になる予定
Proc.new { |receiver| receiver.send *self }
end
end
[bakabon, rerere, hajime].map(&[:greeting, 'さん'])
# => ["こにゃにゃちわー。バカボン さん", "こにゃにゃちわー。レレレのおじさん さん", "こにゃにゃちわー。はじめ さん"]
悪くないと思うのだけど・・・、どうかしら。