Warning for googlers: this post is probably not useful. I wasn’t that good at Ruby metaprogramming when I wrote this post. (The obvious followup question is why I wrote it in the first place. I have no idea.)
I’m implementing a Tree in Ruby for an Euler problem (I don’t think I’m quite good enough to attempt it in Haskell yet). Yes I know there’s probably several in the standard libraries, but that’s cheating.
Anyway, a lot of the methods, like size, each, and so on are easiest implemented as recursive methods of the nodes. Meaning they’re all just calling say @root.size, and nothing else. This is not on.
One of the best things about Ruby is that you can just define new methods for any class, at any time, and moreover you can do this automatically using instance_eval and friends. So this was just dying for a forward_to method which takes a list of names, and forwards those methods to some member or other. The first form is thus1:
module ForwardTo
def forward_to mem, *meths
meths.each do |meth|
instance_eval "def #{meth}; @#{mem}.send :#{meth}; end"
end
end
end
Obviously this doesn’t allow arguments or blocks to be passed, making all kinds of things impossible, but notably each, which takes both (it has an argument specifying the order of traversal), and was the whole point. Luckily this is easily solved, by adding *args and &block. Also for some reason I forgot about %<> quoting2 the first time around, so this iteration is a lot more readable.
module ForwardTo
def forward_to mem, meths
meths.each do |meth|
instance_eval %<
def #{meth}(args, &block)
@#{mem}.send :#{meth}, *args, &block
end
>
end
end
end
end end end end end end end end end… Anyway, you probably already know that send doesn’t honour method visibility, so we could be accidentally calling private methods. So I changed it to —
— And then, just a second ago when writing this, I came to my senses and realised that I’m in an eval so I don’t have to mess around with this sort of thing: I can just have @#{mem}.#{meth} in the string instead. Also, I put the method in Object instead because it’s small and unlikely to mess things up, and it saves a call to include.
class Object
def forward_to mem, meths
meths.each do |meth|
instance_eval %<
def #{meth}(args, &block)
@#{mem}.#{meth}(*args, &block)
end
>
end
end
end
There is still a big problem with this, though. You will probably want to put it in the class body, along with things like attr_accessor:
class Foo
@mem = []
# ...
forward_to :mem, :each, :size
end
… which will not work. The ‘instance’ that instance_eval sees is the instance of Class; in other words, this will add class methods each and size. Which will forward to instance variables. Useful, I know. The workaround I’m using at the moment is to put forward_to calls into initialize. I’ll fix it properly when I find the name of the method that does what I mean.
Update: There’s a not-very-well-hidden method called class_eval, which… does exactly what you’d expect. So you can use that instead of instance_eval and then forward_to can be used in exactly the same way as the attr_* methods. (There’s also a module_eval, incidentally.)