share facebook facebook2 twitter menu hatena pocket slack

2014.04.22 TUE

Ruby: ブロック書きたくない病に罹患してArray#to_procを定義してみた

WRITTEN BY山口 与力

cloudpack の ヤマグチです。

甘い物は好きですか?私は好きです。

sweets = %w(cheese strawberry chocolate)

皆様ご存知の通り、Ruby 1.9以降では

sweets.map {|sweet| sweet.capitalize}
# => ["Cheese", "Strawberry", "Chocolate"]

と書くところを

sweets.map &:capitalize
# => ["Cheese", "Strawberry", "Chocolate"]

とも書くことができます。

&プレフィックスの後にProcでないオブジェクトを置くと、そのオブジェクトの#to_procメソッドを使って暗黙の変換が試みられます。Ruby 1.9以降ではSymbolに#to_procがあらかじめ定義されており、:capitalizeという名前のメソッドを呼び出すProcをブロックに変換した上でイテレータ(ブロックを取るメソッド)に渡すという意味になるため、後者のような書き方ができるということですね。

でもこの書き方、シンボルで名前を指定したメソッドに引数を渡せません。

sweets.map(&:+, " cake")
# => SyntaxError: unexpected ')', expecting end-of-input

sweets.map &(:+, " cake")
# => SyntaxError: unexpected ')', expecting end-of-input

sweets.map(&:+(" cake"))
# => SyntaxError: unexpected ')', expecting end-of-input

こういう場合、普通は素直にブロックを付けて書きます。

sweets.map {|sweet| sweet + " cake"}
# => ["cheese cake", "strawberry cake", "chocolate cake"]

これでも別にいいじゃん。で終わりなのですが、引数を付けるだけなのに……ぐぬぬ、と色々な人たちが&:メソッド名に引数を渡そうと色々な黒魔術を錬成してきたようです(参考参照)。

では、こんなのはどうですかね?

class Array
def to_proc
Proc.new {|object| object.send first, *self[1..-1]}
end
end

sweets.map &[:+, " cake"]
# => ["cheese cake", "strawberry cake", "chocolate cake"]

はい。配列に&を付けてます。配列の中身は1つ目がメソッド名のシンボル、それ以降は引数です。複数引数もいけます。

sweets.map &[:sub, "straw", "blue"]
# => ["cheese", "blueberry", "chocolate"]

おさらいです。&を付けてイテレータに渡されたオブジェクトはそれがProcでない場合#to_procでProcに変換しようとするのでした。

ここではArrayに「配列の先頭要素の名前のメソッドを、2番目以降の要素を引数として呼び出す」という内容の#to_procメソッドを定義してみました。あとは&に配列を渡すだけ……こんな内容のProcが生成され、ブロックとして#mapに渡されているはずです。

Proc.new {|object| object + " cake"}
Proc.new {|object| object.sub "straw", "blue"}

いかがです?

面白いと思った方、参考URLを見るともっと面白いはずです。特に関数型Rubyシリーズは非常にディープでヤバいのでオススメ。

参考

こちらの記事はなかの人(ヤマグチ)監修のもと掲載しています。
元記事は、こちら