RubyでPythonのdecoratorっぽいもの

Pythonのdecorator は格好いいよね。Rubyでこんな感じのものを作れないかと何回か考えたものの、上手いAPIを思いつかずにいた。

このたび、 technohippyさんの挑戦 に刺激されて書いてみた。

仕様としてはPythonに合わせて、呼び出し可能オブジェクトを返す関数としてdecoratorを作成する。そして、declareというメソッドにdecoratorを渡すと、ブロック内で定義されたメソッドはdecoratorで修飾される。

class Module
 def type(name, types)
    before(name) do |name, args|
      args.zip(types).each_with_index do |(arg, type), i|
        raise TypeError, "#{i}th argument #{arg} should be #{type}" unless arg.instance_of?(type)
      end
    end
  end

  def always_one(name)
     proc{|*args| 1 }
  end
end

class Calc
  declare :type, [Float, Float] do
    def add(x,y); x+y end
    def sub(x,y); x-y end
    def mul(x,y); x*y end
  end
  declare :always_one do
    def div(x,y); x/y end
  end
end

calc = Calc.new
p calc.add(1.0, 2.0)
p.calc.div(1.0, 2.0)   #=> 1
p calc.sub(1.0, 2)     #=> TypeError

ポイント

  • Rubyなんだからブロック使おうよ、という。Module#privateみたいに頑張ってブロックなしにするのも考えたけど、労多くして実り少なしだよね。
  • Ruby 1.8.7以降で動作する。
  • 特異メソッドに非対応
  • イテレータは修飾できない。define_methodにブロックを渡した場合、呼び出し時にメソッド本体へブロックを渡す方法がないので。Ruby 1.9だと頑張ればできるんだけど。
  • UnboundMethod経由のメソッドすり替えを試せて楽しかった。
  • もうちょっと真面目に書いてgem化したら誰か使う?