singleton_method_added
Object#singleton_method_added, Object#singleton_method_removedを使うと、特異メソッドの追加と削除を監視できます。
class Rubyco def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" end def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" end end
実行結果です。singleton_method_added, singleton_method_remove自体も特異メソッドですからね♪
singleton_method_added(singleton_method_added) singleton_method_added(singleton_method_removed)
インタプリタさんに意地悪してみましょう♪ 無限ループに落ちるでしょうか。
class Rubyco def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" remove_method(sym) end def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" add_method(sym) end end
実行結果です。singleton_method_addedが呼ばれた時点ではまだ定義されていないんですね。残念です(何が?)。
a.rb:4:in `remove_method': method `singleton_method_added' not defined in Rubyco (NameError) from a.rb:4:in `singleton_method_added' from a.rb:2
追記:id:sumimさんからご指摘をいただきました。remove_methodはメソッドを削除したいオブジェクトのクラスオブジェクトがレシーバ(それはそうですね)なので、上のプログラムは誤りとのことでした。で、sumimさんのサンプルコードを解読しました。Rubycoの特異メソッドをremove_methodするには、Rubycoというオブジェクトではなく、Rubycoというオブジェクトのクラスをレシーバにしなければいけません。そのため、Rubycoというオブジェクト固有のクラスを class << self で得て、class_evalをすることになりました。sumimさん、貴重なご指摘をありがとうございます。
さあ、これでインタプリタ君を無限ループさせることができるでしょうか♪
class Rubyco def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" class <<self self end.class_eval(<<-EOD) puts "remove_method(#{sym})" remove_method(:#{sym}) EOD end def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" class <<self self end.class_eval(<<-EOD) puts "add_method(#{sym})" add_method(:#{sym}) EOD end end
実行結果です。
singleton_method_added(singleton_method_added) remove_method(singleton_method_added)
なるほど。singleton_method_addedというメソッドそのものが特異メソッドなので、即座にremove_methodされてしまったようです。無限ループになりません。
あ、それにadd_methodではなくdefine_methodですね。だめじゃん。
…………。
こ、これでどうだっ。
class Rubyco def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" class <<self self end.class_eval(<<-OUTER) puts "define_method(#{sym})" define_method(:#{sym}, proc { def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" class <<self self end.class_eval(<<-INNER) puts "remove_method(#{sym})" remove_method(:#{sym}) INNER end } ) OUTER end def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" class <<self self end.class_eval(<<-EOD) puts "remove_method(#{sym})" remove_method(:#{sym}) EOD end end
実行結果です。
singleton_method_added(singleton_method_added) remove_method(singleton_method_added) singleton_method_removed(singleton_method_added) define_method(singleton_method_added) singleton_method_added(singleton_method_added) remove_method(singleton_method_added) singleton_method_removed(singleton_method_added) define_method(singleton_method_added) singleton_method_added(singleton_method_added) remove_method(singleton_method_added) singleton_method_removed(singleton_method_added) define_method(singleton_method_added) singleton_method_added(singleton_method_added) remove_method(singleton_method_added) singleton_method_removed(singleton_method_added) define_method(singleton_method_added) ... stack level too deep (SystemStackError) from (eval):2:in `singleton_method_added' from (eval):3:in `singleton_method_added' from (eval):2:in `singleton_method_removed' from (eval):2:in `singleton_method_added' from (eval):3:in `singleton_method_added' from (eval):2:in `singleton_method_removed' from (eval):2:in `singleton_method_added' from (eval):3:in `singleton_method_added' ... 560 levels... from (eval):3:in `singleton_method_added' from (eval):2:in `singleton_method_removed' from (eval):2:in `singleton_method_added' from a.rb:22
やったね♪
さらに追記:sumimさんからのコメントをさらにいただきました。現在解読中…。なるほど。インスタンス変数@saved_methodを間に立てるのですね。
# id:sumimさんから class Rubyco def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" (class << self; self end).class_eval("define_method(:#{sym}, @saved_method)") end def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" instance_specific_class = (class << self; self end) instance_specific_class.instance_variable_set(:@saved_method, method(sym)) instance_specific_class.class_eval("remove_method(:#{sym})") end end
グローバル変数版。
# id:sumimさんから class Rubyco def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" (class << self; self end).class_eval("define_method(:#{sym}, $saved_method)") end def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" $saved_method = method(sym) (class << self; self end).class_eval("remove_method(:#{sym})") end end
sumimさん、さらに改良。
# id:sumimさんから class Rubyco def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" (class << self; self end).class_eval { define_method(sym, @saved_method) } end def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" (class << self; self end).class_eval { @saved_method = Rubyco.method(sym) remove_method(sym) } end end
それならこのほうが、などと。
class Rubyco RubycoClass = class << self; self end def self.singleton_method_removed(sym) puts "singleton_method_removed(#{sym})" RubycoClass.class_eval { define_method(sym, @saved_method) } end def self.singleton_method_added(sym) puts "singleton_method_added(#{sym})" RubycoClass.class_eval { @saved_method = Rubyco.method(sym) remove_method(sym) } end end