A cunning (evil?) trick with Ruby

Posted by Piers Cawley on Aug 8, 2007

One of the handy tools that Ruby makes available to us Domain Specific Pidgin builders is instance_eval. With instance_eval you can take a block that was created in one context, with all its lovely closed over local variables, and evaluate it in the context of an instance of an arbitrary object. Which means that any references to instance variables and calls to methods are made as if your block were a 0 argument method of that class. It’s really potent, but at the same time, a little frustrating.

Frustrating? Why frustrating?

Well, it would be really cool if you could call instance_eval with a block that took some arguments. That way, you could inject some values into the block from still another scope. (Yes, it’s arcane, but in the places where it would be handy it would be really handy).

I just worked out how to do it:

 
def my_instance_eval(*args, &block)
return instance_eval(*args,&block) unless block_given? && !block.arity.zero?
self.class.send(:define_method, :, &block)
returning((*args)) do
self.class.send(:remove_method, :**)
end
end


There’s problems with that though, the most obvious one being that there’s no guarantee that __ won’t already exist as a method or that our block won’t call __. Here’s a safer, if scarier option:



def my_instance_eval(*args, &block)
return instance_eval(*args,&block) unless block_given? && !block.arity.zero?
old_method = (self.class.instance_method(:) rescue nil)
self.class.send(:define_method, :, &block)
block_method = self.class.instance_method(:)
if old_method
self.class.send(:define_method, :, old_method)
else
self.class.send(:remove_method, :**)
end
block_method.bind(self).call(*args)
end


This should work even in the face of being called with something along the lines of object.my_instance_eval(10) {|v| v + __}. Of course, there would be no point in calling like that. You’d only really need it when you want to do something like object.my_instance_eval(10,&a_block_param).

In the project I’m currently working, where the need for this arose, I shall probably extract the body of my_instance_eval to Proc#to_unbound_method, that way, instead of hanging onto Block objects I can hang onto UnboundMethod objects and avoid repeatedly shuffling methods.

But… how does it work?

I hope the code’s reasonably obvious. However…

The essence of it is that real methods get to take arguments, so what we need is some way of turning our block into a real method without altering the behaviour/interface of our object. Ruby does allow us create UnboundMethod objects which can be thought of as anonymous methods. To use them you have to bind them to a specific instance and then call them, which is the last thing we do in my_instance_eval. The intervening code is what turns our Proc into an UnboundMethod. First, we stash the old __ method, or a nil if there wasn’t one. Then we define a new __ using our block, and immediately use instance_method to get it back as an UnboundMethod. Then we either replace the old definition of __ or simply remove the new one, restoring the original behaviour of our class. Then we bind our new anonymous method to self and call it with our arguments. Easy.

What happens if our class has method_removed or method_added implemented? Can we ‘hide’ from them? How?
What does Proc#to_unbound_method look like? Does it need to take an argument?