LoginSignup
3
1

More than 5 years have passed since last update.

[メタプログラミング] Procを使って Enumerator のメソッドを美しく。

Last updated at Posted at 2017-05-04

はじめに

配列やハッシュに使うメソッド、mapとかinjectなどを全てProcを使って美しく書きたい。そんな思いから、Arrayに#to_procを追加してみた。割りと使えると思うんだ。

種類 名前
前提 Procとは
本編 Arrayにto_proc

Procとは

Ruby Doc

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_procMethod#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, 'さん'])
# => ["こにゃにゃちわー。バカボン さん", "こにゃにゃちわー。レレレのおじさん さん", "こにゃにゃちわー。はじめ さん"]

悪くないと思うのだけど・・・、どうかしら。

3
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1