Replacing a Ruby instance method with a closure

I was recently playing around with replacing methods on specific Ruby instances. Everything is open for change in Ruby so I thought it would be a piece of cake. And in a way it was. The catch was that I wanted the method definition to close over some local variables I had declared (the same way () => localVar; will close over localVar for .NET lambdas), and this proved to be a little trickier than I expected.

Basic instance method replacement

Say we have an incredibly exciting Greeter class that says hello:

class Greeter
    def say_hello
        puts "Hello World!"
    end
end

greeter = Greeter.new
greeter.say_hello

#=> Hello World!

We can easily re-define this method on a specific instance like this:

def greeter.say_hello
    puts "Howdy World!"
end
greeter.say_hello
#=> Howdy World!
Note: It is just as easy to redefine this method on all instances of Greeter by opening up the class as Derick points out in the comments. In this case I just want to replace the method on the specific greeter instance, not on all instances of the Greeter class.

Now say that rather than hard-coding a greeting in our re-defined method, we want to reference a local variable instead (useful for replacing methods for unit tests).

name = "Anonymous Dave"
def greeter.say_hello
    puts "Hello #{name}"
end

#This won't work:
#  greeter.say_hello
#It gives this exception:
# in `say_hello': undefined local variable or method `name' for #<Greeter:0x337e1e0> (NameError)

This doesn’t work because when we get to def greeter.say_hello we’ve changed the current scope to the greeter instance, and greeter knows nothing about name; it does not exist in the scope say_hello is running in. What we really want is to capture the name variable via a closure when we define the method, and in Ruby, we can do this with blocks and Procs.

A detour into Ruby internals and other stuff I don’t understand

I’m completely out of my depth here, but here is my Ruby n00bie interpretation of some Ruby mumbo jumbo. First, everything in Ruby is an object, and every object has a class (as near as I can tell). Even nil (Ruby’s null):

puts nil.class
#=> NilClass
puts nil.methods.count
#=> 71

If you’re from a .NET background, we’ve just called null.GetType().GetMethods().Length and lived to tell the tale (and been told 71 apparently :)).

Anyway, the point is that everything is an object and they all have classes. Our greeter object is an instance of the Greeter class we defined earlier. Ruby has all kinds of interesting ways of interacting with instances and classes, including instance_eval and class_eval which let us run code within the context, or scope, of both instances and classes. Unfortunately none of these seem to work the scope in a way that will let us add an instance method to a specific instance using a closure (please let me know if I’m wrong).

There is another option: Ruby metaclasses (a.k.a. eigenclasses). It turns out that when I said that greeter’s class is Greeter, I sort of lied. In fact, Ruby itself will dissemble slightly when you run greeter.class and it says Greeter. Every object has a class, but also a metaclass. And a metaclass’ class has Class, as well as it’s own metaclass, which I guess is a meta-metaclass. And so on into madness.

In a valiant attempt to get back on topic, the idea is that you can add a method to a metaclass that will only be available on the instance. If you modify a method on the class then it will appear on all instances.

The next thing we need to know is that everything that inherits from Class has a very useful method called define_method which defines a method from a block. Unfortunately this method is private. But luckily you can invoke private methods in Ruby using the send method. The send method is also cursed and contains potassium benzoate… kidding.

Believe it or not, we can now solve the original problem.

Instance method replacement with closures

So what we need to do is get access to the greeter instance metaclass, and send a define_method call with a block that closes over our local variable. We can access an instance’s metaclass by using the class << instance syntax, which will change the current scope to instance’s metaclass (so self will refer to the metaclass. This is confusing; see Yehuda’s article for a great explanation). And the rest is fairly straight forward:

greeters_metaclass = class << greeter; self; end
greeters_metaclass.send(:define_method, :say_hello) do
    puts "G'day #{name}"
end
greeter.say_hello
#=> G'day Anonymous Dave

name = "closure"
greeter.say_hello
#=> G'day closure

We now have all the pieces put together. We’ve got our paws on a reference to the instance’s metaclass, and we’ve defined a class using a block (do..end) which will close over our local variable using the metaclass’ private define_method via send. Piece of cake, right? :)

Parting thoughts

This tripped me up for a while, and this post is my attempt to try and decipher what is happening. It’s not quite as bad as I make it sound, it just requires some understanding about Ruby’s class system and closures. Along the way I found these absolutely essential references that will hopefully clear things up a bit for you:

Next post we’ll look at a hack to make this a bit easier.

Comments