せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】【メタプログラミング】チェイン(フック)& 特異メソッド を使ったメソッド呼び出しのトレーシング

 
■topic summary
how to trace method calling with using eigenclass.

 
委譲とmethod_missingを使ったトレーシングは前にやったけど、
今度はチェイン(フック)を使ったトレーシングを実装します。

 

ポイント

特異メソッドを使ったトレーシング

・式展開が2レベルに渡る(特異メソッド定義時、実行時)
インスタンス変数は不要になった時点で削除(@_traced)
・evalで定義するメソッドが、
 ⇒引数にブロックを持つ場合・・・evalの引数は文字列
 ⇒ブロックを持たない場合・・・evalの引数はブロックでOK
・特異クラス生成イディオム(1個め、2個めのselfはそれぞれ別オブジェクトを参照)


 

コード

class Object
  def trace!(*methods)
    @_traced = @_traced || []
    methods = public_methods(false) if methods.size == 0
    methods.map!{|m| m.intern}
    methods -= @_traced
    return if methods.empty?
    @_traced |= methods
    eigenclass = class << self; self; end
    STDOUT << "Tracing #{methods.join(",")} on #{object_id}\n"
    methods.each do |m|
      eigenclass.class_eval %Q{
        def #{m}(*args, &b)
          STDOUT << "Entering #{m}(\#{args.join(",")})\n"
          result = super || "nil"
          STDOUT << "Exiting #{m} with \#{result}\n"
          result
        end
      }
    end
  end
  def untrace!(*methods)
    if methods.size == 0
      methods = @_traced
    else
      methods &= @_traced
    end
    STDOUT << "Untracing #{methods.join(",")} on #{object_id}\n"
    methods.map!{|m| m.intern}
    eigenclass = class << self; self; end
    eigenclass.class_eval do
      methods.each do |m|
        remove_method m
      end
    end
    @_traced -= methods
  end
end
b = [1,2,3]
b.trace!(:index)
b.index("a")
b.untrace!(:index)
b.index("a")

 
■実行結果

Tracing index on 22638228
Entering index(a)
Exiting index with nil
Untracing index on 22638228


 
ふむふむ。