January 25, 2006

Duplicate Message Remover for Thunderbird 1.5

I've updated the Duplicate Message Remover extension to make it compatible with Thunderbird 1.5. Grab it here.

January 20, 2006

Replacing methods

Let's say we want to replace a method instead of just throwing it away. But we want the new method to be able to call the old implementation.

The standard way of doing this is by aliasing the old method and making the new one call the aliased version:


class Foo
  def method
    ...
  end

...

class Foo
  alias :method :old_method

  def method
    ...
    old_method
  end
end



This solution works perfectly for most situations, but what if we want to remove all traces of the old method?

Here's a simple trick to do exactly that:


class Foo
   old_method = instance_method(:method)

   define_method(:method) { |*args|
       # do something here ...
       old_method.bind(self).call(*args)
   }
end


It shouldn't take much to understand how it works, but let's analyze it in more detail. The first line gets a hold of the old implementation of method. instance_method returns an UnboundMethod object that wraps it.

Next, we redefine method and pass it a block with the new implementation. One thing worth mentioning is that the old_method variable inside the block is bound to the variable assigned to outside of the block. The binding is maintained even after the execution reaches the end of the class definition. As a result, the new method has a direct reference to the old one... and nobody else does.

After doing whatever it has to do, the new method calls the old one after binding the UnboundMethod to the current object (i.e., the object on which method was invoked).

If you have a keen eye, you'll notice that there's something special about self. At first glance, it looks like a standard variable that just happens to hold a reference to the current object. If that were true, it would follow that the reference to self inside the block would be bound to the self representing class Foo. That is not the case, though. The binding of self is established when the block gets called.

Labels:

January 17, 2006

Removing methods from a module

How do we remove a method from one of Ruby's built-in modules (Kernel, etc)? It should be easy and obvious, right? The following snippet should be enough, right?


#remove system from module Kernel
Kernel.send :remove_method, :system


Well, not quite. While that prevents you from calling system() in the context of the current object, you can still invoke it like this:


Kernel::system('date')


What's going on?

It turns out that they way Kernel (and other built-in modules) are defined is analogous to doing the following:


module Kernel
  def system (...)
    ...
  end

  module_function :system
end


Thus, system() is a method that will be added to instances of classes that include the given module (in our case, instances of any object, since Kernel is included by Object), but it is also a method in Kernel's singleton class (bear with me for a moment), or a module_method as the PickAxe book calls them. In Java parlance, they are simply static methods.

Looking at Ruby's source code, we can see that all of Kernel's methods are defined by way of the rb_define_module_function function, whose definition is:


void
rb_define_module_function(module, name, func, argc)
  VALUE module;
  const char *name;
  VALUE (*func)();
  int argc;
{
  rb_define_private_method(module, name, func, argc);
  rb_define_singleton_method(module, name, func, argc);
}


So, what does it mean that "system" is a method of Kernel's singleton class? Let's recap what happens when we create a class and add a method to the singleton class of one of it's instances.


class A
end

V1 = A.new    # creates an instance of A and assign it to constant V1
V2 = A.new    # ... and V2

# now we add method x() to V1's singleton class
class << V1
   def x
     puts "hello"
   end
end

V1.x    # will print "hello"
V2.x    # will fail ... V2 does not have a method x()


Clear?

Ok, now, remember that all Ruby modules are instances of a class called Module? Say we replace A by Module, V1 by Kernel and x() by system(). What we get is the following:


Kernel = Module.new

class << Kernel
   def system
     ...
   end
end


And that's essentially what's going on behind the scenes.

Going back to the question that started this post, how do we remove a method from one of the built-in modules, anyway? The answer is easy, if not as obvious as we might have expected:


module Kernel
   remove_method :system
end

class << Kernel
   remove_method :system
end


Or, more concisely,


Kernel.send :remove_method, :system
class << Kernel; self; end.send :remove_method, :system

Labels:

January 10, 2006

Flying dog