( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

「関数型Ruby」という病(1) - Symbol#to_proc, Object#method

この記事は、Rubyを書くにあたって「いかにブロックを書かずにすませるか」を追求した、誰得な連載である。
だって、ブロックって汚いじゃん?仮引数の|x|とかキモイ。

Symbol#to_proc

基本中の基本。

instance method Symbol#to_proc

以下のようなArrayがあって、

irb(main):003:0> arr = [:user, :entry, :article, :comment, :category]
=> [:user, :entry, :article, :comment, :category]


各要素をto_sしたかったら、

irb(main):004:0> arr.map{|s| s.to_s}
=> ["user", "entry", "article", "comment", "category"]


と書くかわりに、

irb(main):005:0> arr.map(&:to_s)
=> ["user", "entry", "article", "comment", "category"]


って書けばいい。


これは、ブロックを受け取るメソッドにProc以外が渡されると、to_procを呼び出して型変換を行う仕組みによる。

to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェ クトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実 行され、Proc オブジェクトを返すことが期待されます。

メソッド呼び出し(super・ブロック付き・yield)


Symbol#to_procの実装は、こんなイメージ。ようは、引数に対してsendでSymbolを送るprocを生成する

def to_proc
  proc{|obj, *args| obj.send(self, *arg)}
end


このようになる。

irb(main):006:0> :to_s.to_proc
=> #<Proc:0x007fcf721c21d8>
irb(main):007:0> :to_s.to_proc.call(:user)
=> "user"

Object#method


Object#methodは大変素晴らしいデス。これ無くして関数型Rubyは語れない。

instance method Object#method


Object#methodは、レシーバーとなったObjectから、Methodオブジェクトを取り出す。
このMethodオブジェクトから、メソッド名をレシーバーに対して呼び出すProcを作り出すことができる。

irb(main):010:0> method = arr.method(:size)
=> #<Method: Array#length>
irb(main):011:0> method.call
=> 3
irb(main):012:0> method.to_proc.call
=> 3

この例では、arrオブジェクトに対するsizeメソッド呼び出しを、Procとして取り出している。
Procとして取り出せるということは、特定のオブジェクトへのメソッド呼び出しがProcという形で可搬性を得ているといえる。
つまり、一度Procにしてしまえば、変数に代入しようが他の関数に引数として渡そうが、やりたい放題絶頂ってワケ。ビバ!ファーストクラス!!


このアプローチで、select, map, injectなどのブロックを取る高階関数群を、ブロックを書くこと無く利用できる。
具体例を示そう。

class Criteria
  def initialize(length)
    @length = length
  end

  def allow?(s)
    s.length >= @length
  end
end

このCriteriaクラスは、allowメソッドで、@lengthでもっている値と引数の値を比較する。
今、lengthが6のCriteriaオブジェクトがあり、それに対してallow?がtrueを返すものだけarrの中から抽出したい。


ブロックを使う場合は、こうなる。

irb(main):053:0> criteria = Criteria.new(6)
=> #<Criteria:0x007fcf71031f90 @length=6>

irb(main):054:0> arr.select{|s| criteria.allow? s }
=> [:article, :comment, :category]


Object#methodを利用すれば、こう書ける。

irb(main):055:0> arr.select(&criteria.method(:allow?))
=> [:article, :comment, :category]

criteriaオブジェクトから、allow?メソッドへの呼び出しをObject#methodで取り出して、selectに渡せばよいだけだ。


Symbol#to_procやObject#methodを使えば、単なるメソッドを呼び出しを行うためだけにわざわざブロックを書かなくてもよくなる。
もちろん、このスタイルが読みやすいコードであるかは賛否両論あるだろうが、ファースクラスな関数の可搬性を意識することでDRYなコードを実現する一助とはなるであろう。


次回は関数合成について書く。( ;゚皿゚)ノシΣ フィンギィィーーッ!!!