I Broke Ruby (i.e. Missing the Brutally Obvious)

M

Michael Judge

It's been yet another late Friday. It's practically a lifestyle by this
point. (I'm glad my wife understands.) So anyway, things were going
fine until I tried to get two ruby files to require each other.
Suddenly the class defined in the first file was considered
"uninitialized."

Here's some code demonstrating the error:

Type into IRB:
require "a"
NameError: uninitialized constant A
from ./b.rb:3
from ./a.rb:1
from (irb):3

Place these files in the same directory you call irb in:

File a.rb
require "b"

class A < Array
# Do nothing
end

File b.rb
require "a"

class B < A
# Do nothing
end

Where's the brutally obvious mistake I made? Why would A be an
uninitialized constant?
 
G

George Ogata

Michael Judge said:
File a.rb
require "b"

class A < Array
# Do nothing
end

File b.rb
require "a"

class B < A
# Do nothing
end

Where's the brutally obvious mistake I made? Why would A be an
uninitialized constant?

Because it hasn't been defined when "class B < A" is evaluated.
Here's what happens, step by step:

- irb evaluates: require "a"
+ ruby parses the 'a.rb' file into an internal representation, doing
syntax checking along the way
+ ruby evaluates the first expression: require "b"
* ruby parses the 'b.rb' file into an internal representation,
doing syntax checking along the way
* ruby evaluates the first expression: require "a". As 'a.rb' has
already been required, this does effectively nothing (see `ri
require').
* ruby evaluates the next expression: class B < A ... end. As A
is not defined, ruby raises the exception you see.

Why does 'a.rb' require 'b.rb' ? Circular requires are a bad thing.
You'll need to break it up.

One option is to just have the toplevel file just have a whole
sequence of requires, and have each source file define what it needs
to without messing around with any requires whatsoever.

Hope this helps.
 
M

Michael Judge

George said:
Because it hasn't been defined when "class B < A" is evaluated.
Here's what happens, step by step:

- irb evaluates: require "a"
+ ruby parses the 'a.rb' file into an internal representation, doing
syntax checking along the way
+ ruby evaluates the first expression: require "b"
* ruby parses the 'b.rb' file into an internal representation,
doing syntax checking along the way
* ruby evaluates the first expression: require "a". As 'a.rb' has
already been required, this does effectively nothing (see `ri
require').
* ruby evaluates the next expression: class B < A ... end. As A
is not defined, ruby raises the exception you see.

Why does 'a.rb' require 'b.rb' ? Circular requires are a bad thing.
You'll need to break it up.

One option is to just have the toplevel file just have a whole
sequence of requires, and have each source file define what it needs
to without messing around with any requires whatsoever.

Hope this helps.

Thanks for your help, George. That's really interesting how the ruby
parser works.

If instead I did, require "b" in irb, it would have worked because the
following sequence would have happened:

1. Parse b.rb until hitting the line: require "a"
2. Parse a.rb (in the process, learning about class A)
3. Continue parsing b.rb
4. Class B's definition (subclassed to A) now makes sense to the parser
and everything works.

Crazy stuff. It makes me want to write a programming language.

In the end, after reading your post, I decided to move class B into the
bottom of a.rb and my unit tests passed. It just seemed simpler than
creating a file that just requires all the different class files.

(Thanks again for taking the time to explain how the parser worked)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top