たごもりすメモ

コードとかその他の話とか。

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なのだが。