やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

Rubyにおけるカリー化(Method.curry)と関数合成(<<)

RubyのMethodクラスの公式ドキュメントを読んでいた所、面白いメソッドを発見しました。
なんとメソッドをカリー化してProcにした値を返してくれるcurryというメソッドがありました。
Rubyではカリー化が簡単には出来ないと思っていたのですが、こんなメソッドが提供されていたとは...。

docs.ruby-lang.org

カリー化は可能だが、関数からProcを返すようにする必要があり面倒...

class Sample
  def self.add(n)
    Proc.new { |m| n + m }
  end
end

f = Sample.add(1)
puts f.class  # Proc
puts f.call(1) # 2

curryメソッドを使ってみる

先ほどはadd(n)と定義しましたがadd(n, m)のままcurryメソッドを使ってカリー化してみます。
合わせてカリー化されたProcを返すcurry_add関数を定義しました。

class Sample
  def self.add(n, m)
    n + m
  end
  
  def self.curry_add
    self.method(:add).curry
  end
end

さて、上手くカリー化されているでしょうか。

f = Sample.curry_add

puts f # #<Proc:0x00001462f13371c0 (lambda)>
puts f.call(1) # #<Proc:0x00001462f1336ab8 (lambda)>
puts f.call(1).call(1) # 2

fにはProcが束縛されています。
さらにfに対してcallメソッドを呼び出してみると別のProcが返ってくることが分かります。
最終的にfに対して2回、callメソッドを呼び出すことで数値を得ることが出来ました。
他の値でも実行可能かどうか、試してみます。

g = Sample.curry_add
add_10 = g.call(10)
puts add_10.call(1) # 11

add_100 = g.call(100)
puts add_100.call(1) # 101

無事にカリー化されているようです。

ついでに関数合成も

同じくMethodクラスに記載のあった関数合成(<<)についても試してみようと思います。
f(x) << g(x)と記述することで、なんとf(g(x))を実現することができるそう。このメソッドについても全く知りませんでした。

docs.ruby-lang.org

class Sample
  def self.f(x)
    x * x
  end
  
  def self.g(x)
    x + x
  end
end

pipe = (Sample.method(:f) << Sample.method(:g))
puts pipe # #<Proc:0x000014efd3dbfe48 (lambda)>
puts pipe.call(3) # 36

関数合成(<<)を行うとProcが返ってきます。つまり、内部的には別の新しい関数をProcで作成しているようです。
作成した合成関数(Proc)は例の如くcallメソッドで呼び出すことが出来ました。

ちなみに>>を呼び出すことで、逆向きの関数合成も可能です。順序が変わったことで結果が変化しました。

pipe = (Sample.method(:f) >> Sample.method(:g))
puts pipe # #<Proc:0x000014e42e8cfee8 (lambda)>
puts pipe.call(3) # 18

いやー、本当に知らないことがたくさんあるんだなと思わされます。
また新しいレシピが増えたので個人的には大満足です。