Friday, August 1, 2008

Apparently code order matters in Ruby

I was recently working on some code that had a structure like this:

module OuterModule
class MyObject
include InnerModule
end

module InnerModule
def hello
"hello"
end
end
end

include OuterModule

m = MyObject.new

puts m.hello

When I ran it, I would get this unfortunate output:

test_ruby_class_heirarchy.rb:3: uninitialized constant OuterModule::MyObject::InnerModule (NameError)

I'm too embarrased to list all the voodoo I tried to get this to work, but simply putting the definition of MyObject below that of InnerModule did the trick:

module OuterModule
module InnerModule
def hello
"hello"
end
end

class MyObject
include InnerModule
end
end

include OuterModule

m = MyObject.new

puts m.hello

This gives me the expected output:

hello

Going into this, I would have never guessed that the order in which I defined things would have this effect.

2 comments:

malu said...

In compiled languages as C/C++ etc it is normal that you need to define everything before you can use it somehow (yes, there are exceptions...).
I don't know how the Ruby interpreter intern works, but I guess it simply wants to include the module InnerModule as soon as it reads the include statement. At that time the module InnerModule isn't defined so the interpreter doesn't know what to do and throws an exception. But this exception is strange, shouldn't it be InnerModule and not OuterModule?

If someone knows the process the Ruby interpreter uses intern, I would like to learn a bit about it, espacially with this problem. :)

foca said...

The "problem" is that ruby evaluates your code dynamically. So the moment you call "include InnerModule" you are not "declaring that this class will include the module", you are calling include with InnerModule (the implicit receiver--self--inside a class declaration is the class itself--this is how rails let's you call "has_many" or stuff like that). And, at the moment you call this, InnerModule doesn't exist in the OuterModule scope.

As malu said, this is not a language where a compiler can first evaluate what your symbols are and know what you're referring to. Since this is evaluated as you declare it, you actually do need to have things declared beforehand.