Wednesday, March 19, 2008

Ruby Method References In A One-Pass Interpreter

Ruby allows for some very clever techniques involving run-time code evaluation. I find these particularly handy when writing table driven code (e.g. file parsers). But coder beware. There's been a lot of discussion about whether Ruby blocks are truly closures but the fact that it's a one-pass interpreter (at least the reference implementation I'm using, ruby 1.8.5) can cause some unexpected results as well.

Consider this simple case: initializing a class property using a method reference. The Object.method call will succeed or fail (return nil) depending on when it's called in the class definition. First, let's put the initialization at the top of the class (where most of us would normally put it):

class TestMeth

@@method_ref = self.method(:meth)

def self.meth
  puts "executing meth()"
end

def run_method
  @@method_ref.call
end

end

tm = TestMeth.new
tm.run_method

Sorry, no can do:

[chrisa@doppio ~]$ ruby t.rb
t.rb:3:in `method': undefined method `meth' for class `Class' (NameError)
from t.rb:3

Now move the assignment to the other side of the method definition:

class TestMeth

def self.meth
  puts "executing meth()"
end

@@method_ref = self.method(:meth)

def run_method
  @@method_ref.call
end

end

tm = TestMeth.new
tm.run_method

That works:

[chrisa@doppio ~]$ ruby t.rb
executing meth()

The behavior is the same if you make @@method_ref a constant (i.e. 'METHOD_REF'). The interpreter hasn't gotten to that line of the file when the assignment is executed. If you're using method references, you either have to position them correctly in the file or assign them at run-time in an initializer method. Works, but not as you might expect.

No comments: