K.Sasada's Home Page

Diary - 2013 November

研究日記

霜月

_19(Tue)

旧聞になってしまいますが、RubyConf2013@Miami beach で発表してきました。

K.Sasada: Object management on Ruby 2.1, Oral Presentation at 13th Inernational Ruby Conference. at Miami beach, Florida on November 9th to 11th of 2013. rubyconf2013-ko1_pub.pdf (1288KB)

なお、すべての発表は http://www.atdot.net/~ko1/activities/ にまとめてあります。


特定のメソッドの呼び出し履歴が欲しい、みたいなケースは時々あるんじゃないかと思います。

というわけで、Ruby 2.0 から導入された Module#prepend (と、一応 keyword 引数)を利用して、そういう履歴を簡単に取得するためのメソッドを書いてみました。

class MethodTracer
  class AllTracer
    attr_reader :records
    def initialize
      @records = []
    end

    def record args, call_site, result
      @records << [args, call_site, result]
    end

    def clear
      @records.clear
    end
  end

  class StatisticsTracer
    attr_reader :args, :call_sites, :results
    def initialize
      @args = Hash.new(0)
      @call_sites = Hash.new(0)
      @results = Hash.new(0)
    end

    def record args, call_site, result
      @args[args] += 1
      @call_sites[call_site.to_s] += 1
      @results[result] += 1
    end

    def clear
      [@args, @call_sites, @results].each{|col| col.clear}
    end
  end

  def self.trace(cls, mid, statistics: false)
    tracer = (statistics ? StatisticsTracer : AllTracer).new
    mod = Module.new{
      define_method(mid){|*args|
        result = super(*args)
        tracer.record(args, caller_locations(1, 1)[0], result)
        result
      }
    }
    cls.prepend mod
    tracer
  end
end

class C
  def foo(a, b, c)
    self
  end
end

require 'pp'
tracer = MethodTracer.trace(C, :foo)
#
C.new.foo(1, 2, 3)
C.new.foo(4, 5, 6).foo(1, 2, 3)
#
pp tracer.records
#=>
# [[[1, 2, 3], "t.rb:59:in `<main>'", #<C:0x2b68d64>],
#  [[4, 5, 6], "t.rb:60:in `<main>'", #<C:0x2b68cec>],
#  [[1, 2, 3], "t.rb:60:in `<main>'", #<C:0x2b68cec>]]

tracer = MethodTracer.trace(C, :foo, statistics: true)
#
C.new.foo(1, 2, 3)
C.new.foo(4, 5, 6).foo(1, 2, 3)
#
pp tracer.args
pp tracer.call_sites
pp tracer.results
#=>
# {[1, 2, 3]=>2, [4, 5, 6]=>1}
# {"t.rb:60:in `<main>'"=>1, "t.rb:61:in `<main>'"=>2}
# {#<C:0x3426834>=>1, #<C:0x3426730>=>2}

MethodTracer.trace に、トレースしたいクラスとメソッドを指定します。


久々に Ruby script をちゃんと書いた。

いや、テストも無いのにちゃんと書いたとか言うな、って感じだろうかのう。


今時だと gist にはったりするんだろうけど、どうやって使うのかわからないという上弱。せめて色づけくらいはしたいのう。

ちなみに、untrace がないのは問題か?


調子に乗って、もう一つ。

今時、キーワード引数に対応していないようなレガシーなメソッドを書く人なんて居ないと思いますが、まぁ、そんなメソッドも時にはあるかもしれません。というわけで、すべてのメソッドをキーワード引数に対応する keyworder メソッドを書いてみました。

def keyworder cls, mid
  method = cls.instance_method(mid)
  mod = Module.new{
    define_method(mid){|**kw|
      params = []
      method.parameters.each{|(_, param)|
        params << kw.fetch(param)
      }
      super(*params)
    }
  }
  cls.prepend mod
end

class C
  def foo a, b, c
    p [a, b, c]
  end
end

keyworder C, :foo
C.new.foo(a: 1, b: 2, c: 3)
#=> [1, 2, 3]

C#foo は keyword 引数未対応ですが、ちゃんと C.new.foo(a: 1, b: 2, c: 3) のように、キーワード引数で呼べるようになっているのがわかるかと思います。

Sasada Koichi / ko1 at atdot dot net
$Date: 2003/04/28 10:27:51 $