rubyで関数合成を適当に
評価関数を扱うコードを書いたりしてると関数合成は割とやりたいことがあって、でも細かいことを考えないといけなかったりでめんどいなーとスルーすることが多いんだけど、このエントリを今日見て刺激を受けた。
Groovy 1.8 のクロージャ合成 - A Memorandum
書いてみた。
1.9でしか動きません。(1.9.2でのみ確認。)
class Proc def +(other) if self.lambda? and other.lambda? lambda {|*x| other.call(self.call(*x)) } elsif not self.lambda? and not other.lambda? Proc.new {|*x| other.call(self.call(*x)) } else raise ArgumentError, "lambda/Proc type mismatch" end end end
RubyのProcオブジェクトはProc.newした場合とlambdaで生成した場合で動作が違ってて、何も考えずに合成するとそのへんで混乱起きそうだなーと思ってたんだけど、1.9でのリファレンス見てみたらProc#lambda? なんていうステキなメソッドが増えてるじゃん!
ということで、lambdaはlambdaで、Proc.newはProc.newで生成するようにしてさくっと書いたのが上の例。
irb上で元と同じ例を実行するとこんな。
plus2 = lambda {|x| x + 2} # => #<Proc:0x000001010faff8@(irb):39 (lambda)> times3 = lambda {|x| x * 3} # => #<Proc:0x000001010a58c8@(irb):40 (lambda)> comp1 = times3 + plus2 # => #<Proc:0x000001010955e0@(irb):17 (lambda)> comp1.call(3) # => 11 comp1.call(4) # => 14
高校の数学で (f・g)(x) == g(f(x)) だってやったよね? ということで、f + g で g(f(x)) になるようにしてある。まあこれでいいでしょ。
難点
上のコードを見れば誰でも分かるが、合成語のProcオブジェクトの #call を呼ぶと、合計で3回も #call の呼び出しが起きる。激しくループが回る処理の中で評価関数にこういうことやると、メソッド呼び出しのオーバーヘッドだけでも馬鹿にならない気がするんだよなー。
一回の Proc#call と同コストで実現するには処理系のハックが必要? もっと上手く実装できるのかな?
あと書いてみて、ActiveSupportとかに既にありそうだなーと思った(調べてない)。
ところで
俺がいま書いている評価関数を云々するコードはPerlなのだが。