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