circular 'require'

S

Shadowfirebird

Hi,

I'm new to ruby and I'm really enjoying it. However, there is one thing...

I gather from this list and my poor efforts that ruby does not like
files that 'require' each other circularly. I can find nothing in the
documentation about this, though; in fact, the description of require
seems to actually rule it out. Can anyone explain what is going on
here?

(This happens in 1.8 and 1.9 and JRuby 1.0. So it's definitely me,
not Ruby, that has the problem.)


To flesh things out a little:

'require' is supposed to load files, unless they have been already
loaded. It stores an array of loaded files in $" for this purpose.

Okay, suppose we have two programs in two different files:

# test1.rb
require "test2.rb"
class One
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end

# test2.rb
require "test1.rb"
class Two
def self.testtwo(); One.whoisone; end
def self.whoistwo(); puts "class two"; end
end
ruby -w test1.rb
/test1.rb:4: unititialized constant Two (NameError)

Why does this happen? I would have thought that test1 would load
test2; then test2 would go to load test1 but find it already loaded.
Apparently what happens is that test2 doesn't get loaded at all.

OTOH if I replace the 'require' with a homebrew, like this:

# test1.rb
$" << "test1.rb"
load("test2.rb") if !$".include?("test2.rb")
...

...then everything works fine, which seems to imply that 'replace'
isn't behaving as described in the slightest.

Anyone that can provide a description of what replace actually does
while staying high-level enough for my head not to spin, will earn my
undying gratitude.

Ta,
Shadowfirebird.

--
Me, I imagine places that I have never seen / The colored lights in
fountains, blue and green / And I imagine places that I will never go
/ Behind these clouds that hang here dark and low
But it's there when I'm holding you / There when I'm sleeping too /
There when there's nothing left of me / Hanging out behind the
burned-out factories / Out of reach but leading me / Into the
beautiful sea
 
S

Stefano Crocco

Hi,

I'm new to ruby and I'm really enjoying it. However, there is one
thing...

I gather from this list and my poor efforts that ruby does not like
files that 'require' each other circularly. I can find nothing in the
documentation about this, though; in fact, the description of require
seems to actually rule it out. Can anyone explain what is going on
here?

(This happens in 1.8 and 1.9 and JRuby 1.0. So it's definitely me,
not Ruby, that has the problem.)


To flesh things out a little:

'require' is supposed to load files, unless they have been already
loaded. It stores an array of loaded files in $" for this purpose.

Okay, suppose we have two programs in two different files:

# test1.rb
require "test2.rb"
class One
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end

# test2.rb
require "test1.rb"
class Two
def self.testtwo(); One.whoisone; end
def self.whoistwo(); puts "class two"; end
end


./test1.rb:4: unititialized constant Two (NameError)

Why does this happen? I would have thought that test1 would load
test2; then test2 would go to load test1 but find it already loaded.
Apparently what happens is that test2 doesn't get loaded at all.

The required file is added to $" only after its contents have been processed,
so when the line

require 'test1.rb'

is executed, the file 'test1.rb' hasn't as yet inserted into $". To be more
specific, here's what happens when the line

require 'test1.rb'

is executed:

* the file test1.rb is read and parsed
* starts the execution of test1.rb
* the line require 'test2.rb' is executed. This means:
* the file test2.rb is read and parsed
* starts the execution of test2.rb
* the line require 'test1.rb' is executed. This means:
* the file test1.rb is read and parsed
* starts the execution of test1.rb
* the line require 'test2.rb' is executed. This means:
...

As you can see, this leads to an endless loop.

Can I ask you why do you need these circular requires? It's a situation which
seldom happens using ruby.

Also, using require, you don' need to specify the .rb extension.

I hope this helps

Stefano
 
S

Shadowfirebird

Thanks for the explanation, but this does rather worry me. It seems
to be a limitation without documentation or good reason. No doubt it
makes sense to those of you with more experience or Ruby and OOP in
general...


As to why I want to create files with circular references? That's a
perfectly good question (but, I think, a seperate one).

Principal-of-least-astonishment answer: I don't wish to be
contentious, but why should I not? There is nothing in the pickaxe or
the API docs that says this should be avoided. And it's not
unreasonable for my objects to talk to each other in a fairly complex
way. My program worked fine when it was a single file.

Technical answer: I have a bunch of classes that all inherit, directly
or indirectly, from a root class. The root class stores an array of
all objects created in a class variable. It also has some static
methods so that the child classes can get information from that array;
some of these classes use kind_of?() to tell different objects apart.
So the child classes call static methods in the root class, and these
static methods in turn refer to the child classes.

Whether this is good design or not is open to question. I'd be very
interested in suggestions for alternative approaches.

But, as I say, I think that's a seperate question. Can anyone tell me
why require isn't coded to cope with circular references, since it
appears to be a relatively simple problem to solve?
 
A

Alex Gutteridge

Hi,

I'm new to ruby and I'm really enjoying it. However, there is one
thing...

I gather from this list and my poor efforts that ruby does not like
files that 'require' each other circularly. I can find nothing in the
documentation about this, though; in fact, the description of require
seems to actually rule it out. Can anyone explain what is going on
here?

(This happens in 1.8 and 1.9 and JRuby 1.0. So it's definitely me,
not Ruby, that has the problem.)


To flesh things out a little:

'require' is supposed to load files, unless they have been already
loaded. It stores an array of loaded files in $" for this purpose.

Okay, suppose we have two programs in two different files:

# test1.rb
require "test2.rb"
class One
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end

# test2.rb
require "test1.rb"
class Two
def self.testtwo(); One.whoisone; end
def self.whoistwo(); puts "class two"; end
end

./test1.rb:4: unititialized constant Two (NameError)

Why does this happen? I would have thought that test1 would load
test2; then test2 would go to load test1 but find it already loaded.
Apparently what happens is that test2 doesn't get loaded at all.


eh? Your example works fine for me on 1.8.6 unless I'm missing
something? There also doesn't appear to be any mention of Two on line
4 of your test1.rb file, has something changed in the cut&paste?

[alexg@powerbook]/Users/alexg/Desktop(25): cat test1.rb
require "test2.rb"
class One
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end
[alexg@powerbook]/Users/alexg/Desktop(26): cat test2.rb
require "test1.rb"
class Two
def self.testtwo(); One.whoisone; end
def self.whoistwo(); puts "class two"; end
end
[alexg@powerbook]/Users/alexg/Desktop(27): ruby test1.rb
[alexg@powerbook]/Users/alexg/Desktop(28): ruby test2.rb

Also, I don't think Stefano's explanation can be quite right - if it
went into an infinite loop as he describes you would never see an
error message, it would just never end...

Alex Gutteridge

Department of Biochemistry
University of Cambridge
 
S

Stefano Crocco

Also, I don't think Stefano's explanation can be quite right - if it =A0
went into an infinite loop as he describes you would never see an =A0
error message, it would just never end...

You're perfectly right. I don't know what I was thinking of when I wrote th=
at=20
mail (actually, I do. I read the subject line and didn't pay enough attenti=
on=20
to the message body. Then, I didn't even think to check whether things were=
=20
indeed going as I thought). Forget my answer completely, and sorry for givi=
ng=20
a wrong information.

Stefano
 
C

Calamitas

require 'test1.rb'

is executed:

* the file test1.rb is read and parsed
* starts the execution of test1.rb
* the line require 'test2.rb' is executed. This means:
* the file test2.rb is read and parsed
* starts the execution of test2.rb
* the line require 'test1.rb' is executed. This means:
* the file test1.rb is read and parsed
* starts the execution of test1.rb
* the line require 'test2.rb' is executed. This means:
...

Actually, I'm neither seeing the error the OP got, neither the endless
loop you seem to get. Looks like a file that is required gets added to
$" before its contents is executed, and that a file passed on the
command line -- like the OP did, so not using the -r switch -- is not.
So basically the execution is as follows:

* the file test1.rb is read and parsed
* starts the execution of test1.rb
* the line require 'test2.rb' is executed. This means:
* the file test2.rb is read and parsed
* adds "test2.rb" to $"
* the line require 'test1.rb' is executed. This means:
* the file test1.rb is read and parsed
* adds "test1.rb" to $"
* the line require 'test1.rb' is executed. Does nothing as
"test1.rb" is in $"
* The rest of test1.rb is executed
* The rest of test2.rb is executed
* The rest of test2.rb is executed again

test1.rb is executed twice, but what probably caused the error in the
OP's original program (the OP pared it down to a point where it
actually did "work") is the fact that "the rest of test1.rb" is
executed before "the rest of test2.rb". It can probably be solved by
doing "ruby -r test1.rb" instead, but that's icky. Generally, if I
*were* to use circular requires, I'd never put "executable code" in
either file, just "declarations". I don't use them though. Not anymore
after I've been bitten a few times.

Peter
 
S

Shadowfirebird

I think that Stefano is assuming that Ruby has some sort of defense
from infinite loops. Which is not unreasonable, but I've no idea if
he is right. Other than that, his explanation fits perfectly with my
perception of events.

Interesting. Yes, something got lost in the cut and paste: One
should inherit from Two. I guess I just fiddled with the example and
didn't retest it; dumb of me.

Very odd how it works if you remove that, since test1.rb is referring
to Two anyway.

test1.rb should read:

require "test2.rb"
class One < Two
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end

$ ruby -w test1.rb
/test1.rb:2: uninitialized constant Two (NameError)
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in
`gem_original_require'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in
`require'
from ./test2.rb:1
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in
`gem_original_require'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in
`require'
from test1.rb:1




Hi,

I'm new to ruby and I'm really enjoying it. However, there is one
thing...

I gather from this list and my poor efforts that ruby does not like
files that 'require' each other circularly. I can find nothing in the
documentation about this, though; in fact, the description of require
seems to actually rule it out. Can anyone explain what is going on
here?

(This happens in 1.8 and 1.9 and JRuby 1.0. So it's definitely me,
not Ruby, that has the problem.)


To flesh things out a little:

'require' is supposed to load files, unless they have been already
loaded. It stores an array of loaded files in $" for this purpose.

Okay, suppose we have two programs in two different files:

# test1.rb
require "test2.rb"
class One
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end

# test2.rb
require "test1.rb"
class Two
def self.testtwo(); One.whoisone; end
def self.whoistwo(); puts "class two"; end
end


./test1.rb:4: unititialized constant Two (NameError)

Why does this happen? I would have thought that test1 would load
test2; then test2 would go to load test1 but find it already loaded.
Apparently what happens is that test2 doesn't get loaded at all.


eh? Your example works fine for me on 1.8.6 unless I'm missing something?
There also doesn't appear to be any mention of Two on line 4 of your
test1.rb file, has something changed in the cut&paste?

[alexg@powerbook]/Users/alexg/Desktop(25): cat test1.rb
require "test2.rb"
class One
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end
[alexg@powerbook]/Users/alexg/Desktop(26): cat test2.rb
require "test1.rb"
class Two
def self.testtwo(); One.whoisone; end
def self.whoistwo(); puts "class two"; end
end
[alexg@powerbook]/Users/alexg/Desktop(27): ruby test1.rb
[alexg@powerbook]/Users/alexg/Desktop(28): ruby test2.rb

Also, I don't think Stefano's explanation can be quite right - if it went
into an infinite loop as he describes you would never see an error message,
it would just never end...

Alex Gutteridge

Department of Biochemistry
University of Cambridge



--
Me, I imagine places that I have never seen / The colored lights in
fountains, blue and green / And I imagine places that I will never go
/ Behind these clouds that hang here dark and low
But it's there when I'm holding you / There when I'm sleeping too /
There when there's nothing left of me / Hanging out behind the
burned-out factories / Out of reach but leading me / Into the
beautiful sea
 
S

Shadowfirebird

*Is* there any executable code in my two example programs, other than
the "require"s?


Actually, I'm neither seeing the error the OP got, neither the endless
loop you seem to get. Looks like a file that is required gets added to
$" before its contents is executed, and that a file passed on the
command line -- like the OP did, so not using the -r switch -- is not.
So basically the execution is as follows:

* the file test1.rb is read and parsed
* starts the execution of test1.rb
* the line require 'test2.rb' is executed. This means:
* the file test2.rb is read and parsed
* adds "test2.rb" to $"
* the line require 'test1.rb' is executed. This means:
* the file test1.rb is read and parsed
* adds "test1.rb" to $"
* the line require 'test1.rb' is executed. Does nothing as
"test1.rb" is in $"
* The rest of test1.rb is executed
* The rest of test2.rb is executed
* The rest of test2.rb is executed again

test1.rb is executed twice, but what probably caused the error in the
OP's original program (the OP pared it down to a point where it
actually did "work") is the fact that "the rest of test1.rb" is
executed before "the rest of test2.rb". It can probably be solved by
doing "ruby -r test1.rb" instead, but that's icky. Generally, if I
*were* to use circular requires, I'd never put "executable code" in
either file, just "declarations". I don't use them though. Not anymore
after I've been bitten a few times.

Peter



--
Me, I imagine places that I have never seen / The colored lights in
fountains, blue and green / And I imagine places that I will never go
/ Behind these clouds that hang here dark and low
But it's there when I'm holding you / There when I'm sleeping too /
There when there's nothing left of me / Hanging out behind the
burned-out factories / Out of reach but leading me / Into the
beautiful sea
 
C

Calamitas

*Is* there any executable code in my two example programs, other than
the "require"s?

The code you posted here works as is. Nowhere is the reference to Two
executed *in the code you posted*. If I execute your code *as you
posted it here*, it doesn't raise the exception, it only gives some
warnings about redefining methods. Note that he error message you
posted refers to a reference to the constant Two in line 4, but in the
code *you posted here*, the only reference is on line 3. So I can only
assume that the error you get is caused by some code you have cut out
before you posted. Something needed to call one.testone, but it is not
*in the code you posted*. You *must* have had executable code when you
got that error.

Peter
 
C

Calamitas

I think that Stefano is assuming that Ruby has some sort of defense
from infinite loops. Which is not unreasonable, but I've no idea if
he is right. Other than that, his explanation fits perfectly with my
perception of events.

Interesting. Yes, something got lost in the cut and paste: One
should inherit from Two. I guess I just fiddled with the example and
didn't retest it; dumb of me.

Very odd how it works if you remove that, since test1.rb is referring
to Two anyway.

test1.rb should read:

require "test2.rb"
class One < Two
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end

I see. I quoted "executable code" because declarations in Ruby too are
executable code. Everything is. Even require is. The execution order I
posted in my other post shows you the problem: everything after the
require in test1.rb is executed before everything after the require in
test2.rb, so you inherit from Two before Two is defined. To me, the
reference to Two in the class definition is executable code. Really,
it is, you can put any expression there:

inherit_from_two = true

class One < (inherit_from_two ? Two : Three)
end

Peter
 
S

Shadowfirebird

I did indeed miss something out from the code I originally posted --
but it was an inheritance, not something executable. the definition
of One in testa.rb should read "Class One < Two". (See my earlier
post.)



The code you posted here works as is. Nowhere is the reference to Two
executed *in the code you posted*. If I execute your code *as you
posted it here*, it doesn't raise the exception, it only gives some
warnings about redefining methods. Note that he error message you
posted refers to a reference to the constant Two in line 4, but in the
code *you posted here*, the only reference is on line 3. So I can only
assume that the error you get is caused by some code you have cut out
before you posted. Something needed to call one.testone, but it is not
*in the code you posted*. You *must* have had executable code when you
got that error.

Peter



--
Me, I imagine places that I have never seen / The colored lights in
fountains, blue and green / And I imagine places that I will never go
/ Behind these clouds that hang here dark and low
But it's there when I'm holding you / There when I'm sleeping too /
There when there's nothing left of me / Hanging out behind the
burned-out factories / Out of reach but leading me / Into the
beautiful sea
 
C

Calamitas

I did indeed miss something out from the code I originally posted --
but it was an inheritance, not something executable. the definition
of One in testa.rb should read "Class One < Two". (See my earlier
post.)

See my later post. ;-)

Peter
 
S

Stefano Crocco

*Is* there any executable code in my two example programs, other than
the "require"s?

The short answer to your question is that in ruby everything is "executable
code", including class and method definitions.

The "executable code" Peter refers to is that code which is executed
immediately when the script is required/loaded/called. This is any kind of
code except that enclosed in method definitions and blocks. Code in method
bodies is executed only when the method is called. Code in blocks is executed
only when the block is called. All other code is executed as soon as it's
seen.

This explains why the line

def self.testone(); Two.whoistwo; end

in class One doesn't cause trouble: the body of the method won't be executed
until the method is called. Until then, ruby has no interest in looking for a
constant called Two. This greatly reduces the need of recursive requires: a
class or constant need only to be defined when the code which uses it is
actually executed, not when it is 'read'.

Regarding why your code doesn't work, I think Peter is almost correct in his
analisys, except when he states that the name of the file is stored in $"
before being loaded, in the require case. After actually testing your code
(which I should have done before sending my first mail), I went looking in the
ruby source code to try to understand why the behavior I described wasn't the
observed one. From what I understand (I'm not very good at reading C code), it
seems that ruby has a mechanism which avoids exactly this kind of endless
loop: when it starts requiring a file, it stores it in some kind of table
(which as nothing to do with $"), where it remains until it's been 'fully'
required (that is, until the code it contains, including other requires, has
been executed). While the file is in the table, attempts to require it again
fail, just as it had been put in $". Only at the end of this process, the file
is added to $". For those interested, the involved C functions are
rb_require_safe and load_lock, both in eval.c in the ruby distribution.

Unfortunately, my skills haven't been enough to understand what happens when
the file is given as a argument to ruby, rather than required.

Sorry for the wrong information I gave before

Stefano
 
M

Michael T. Richter

[Note: parts of this message were removed to make it a legal post.]

test1.rb is executed twice, but what probably caused the error in the
OP's original program (the OP pared it down to a point where it
actually did "work") is the fact that "the rest of test1.rb" is
executed before "the rest of test2.rb". It can probably be solved by
doing "ruby -r test1.rb" instead, but that's icky. Generally, if I
*were* to use circular requires, I'd never put "executable code" in
either file, just "declarations". I don't use them though. Not anymore
after I've been bitten a few times.


Here's what the OP was talking about, I suspect, taken down to its
briefest possible example:


$ cat test1.rb
require 'test2'
class NeededInTest2
@@funny_stuff = NeededInTest1.new
end

michael@isolde:~/junk$ cat test2.rb
require 'test1'
class NeededInTest1
@@funny_stuff = NeededInTest2.new
end

michael@isolde:~/junk$ ruby test1.rb
./test1.rb:3: uninitialized constant NeededInTest2::NeededInTest1 (NameError)
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
from ./test2.rb:1
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
from test1.rb:1


This is the problem boiled down to its essence. There is no easy way
out of this either that doesn't either a) mangle the architecture or b)
fail. You can't use something like C/++'s "include guards" to get
around this, nor can you use any kind of super-class elevation trick or
the like. What would be needed to solve this issue would be for ruby to
have a proper sense of modules and do linkage across those instead of
treating the entire tree of requires as, in effect, one ginormous text
file.
 
C

Calamitas

Regarding why your code doesn't work, I think Peter is almost correct in his
analisys, except when he states that the name of the file is stored in $"
before being loaded, in the require case. After actually testing your code
(which I should have done before sending my first mail), I went looking in the
ruby source code to try to understand why the behavior I described wasn't the
observed one. From what I understand (I'm not very good at reading C code), it
seems that ruby has a mechanism which avoids exactly this kind of endless
loop: when it starts requiring a file, it stores it in some kind of table
(which as nothing to do with $"), where it remains until it's been 'fully'
required (that is, until the code it contains, including other requires, has
been executed). While the file is in the table, attempts to require it again
fail, just as it had been put in $". Only at the end of this process, the file
is added to $". For those interested, the involved C functions are
rb_require_safe and load_lock, both in eval.c in the ruby distribution.

Good to know it works this way. I wanted to test whether it was
actually stored in $", but I was too lazy. Dang! ;-) But either way,
something had to break the infinite loop because there was none.

Peter
 
S

Shadowfirebird

I found a very easy way around it. This is what bothers me. If it's
that easy, why doesn't require do it?

# test1.rb
$" << "test1.rb"
load("test2.rb") unless $".include?("test2.rb")
class One < Two
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end

# test2.rb
$" << "test2.rb"
load("test1.rb") unless $".include?("test1.rb")
class Two
def self.testtwo(); One.whoisone; end
def self.whoistwo(); puts "class two"; end
end


Here's what the OP was talking about, I suspect, taken down to its
briefest possible example:


$ cat test1.rb
require 'test2'
class NeededInTest2
@@funny_stuff = NeededInTest1.new
end

michael@isolde:~/junk$ cat test2.rb
require 'test1'
class NeededInTest1
@@funny_stuff = NeededInTest2.new
end

michael@isolde:~/junk$ ruby test1.rb
./test1.rb:3: uninitialized constant NeededInTest2::NeededInTest1 (NameError)
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
from ./test2.rb:1
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
from /home/michael/software/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
from test1.rb:1


This is the problem boiled down to its essence. There is no easy way
out of this either that doesn't either a) mangle the architecture or b)
fail. You can't use something like C/++'s "include guards" to get
around this, nor can you use any kind of super-class elevation trick or
the like. What would be needed to solve this issue would be for ruby to
have a proper sense of modules and do linkage across those instead of
treating the entire tree of requires as, in effect, one ginormous text
file.

--
Michael T. Richter <[email protected]> (GoogleTalk:
(e-mail address removed))
Theory is knowledge that doesn't work. Practice is when everything works
and you don't know why. (Hermann Hesse)



--
Me, I imagine places that I have never seen / The colored lights in
fountains, blue and green / And I imagine places that I will never go
/ Behind these clouds that hang here dark and low
But it's there when I'm holding you / There when I'm sleeping too /
There when there's nothing left of me / Hanging out behind the
burned-out factories / Out of reach but leading me / Into the
beautiful sea
 
M

Michael T. Richter

[Note: parts of this message were removed to make it a legal post.]

I found a very easy way around it. This is what bothers me. If it's
that easy, why doesn't require do it?



Try my code with that:


michael@isolde:~/junk$ cat test1.rb
$" << "test1.rb"
load("test2.rb") unless $".include?("test2.rb")
class NeededInTest2
@@funny_stuff = NeededInTest1.new
end

michael@isolde:~/junk$ cat test2.rb
$" << "test2.rb"
load("test1.rb") unless $".include?("test1.rb")
class NeededInTest1
@@funny_stuff = NeededInTest2.new
end

michael@isolde:~/junk$ ruby test1.rb
./test2.rb:4: uninitialized constant NeededInTest1::NeededInTest2 (NameError)
from test1.rb:2:in `load'
from test1.rb:2


The real solution remains a proper system of modules in Ruby.
 
S

Shadowfirebird

Yes, I can see now that in your example no order of loading will allow
the objects to be defined before they are used. That is certainly a
trap to avoid. Thanks, Michael.

The annoying thing is that my actual code worked "fine" when it was
all in one file but stopped working when I split the objects into
different files. And of course, it's too long to post here.

I may come back and ask different questions once I've had a bit of a
think. Thanks to everyone.

Shadowfirebird.

Try my code with that:


michael@isolde:~/junk$ cat test1.rb
$" << "test1.rb"
load("test2.rb") unless $".include?("test2.rb")
class NeededInTest2
@@funny_stuff = NeededInTest1.new
end

michael@isolde:~/junk$ cat test2.rb
$" << "test2.rb"
load("test1.rb") unless $".include?("test1.rb")
class NeededInTest1
@@funny_stuff = NeededInTest2.new
end

michael@isolde:~/junk$ ruby test1.rb
./test2.rb:4: uninitialized constant NeededInTest1::NeededInTest2 (NameError)
from test1.rb:2:in `load'
from test1.rb:2


The real solution remains a proper system of modules in Ruby.



--
Michael T. Richter <[email protected]> (GoogleTalk:
(e-mail address removed))
It's OK to figure out murder mysteries, but you shouldn't need to figure
out code. You should be able to read it. (Steve McConnell)



--
Me, I imagine places that I have never seen / The colored lights in
fountains, blue and green / And I imagine places that I will never go
/ Behind these clouds that hang here dark and low
But it's there when I'm holding you / There when I'm sleeping too /
There when there's nothing left of me / Hanging out behind the
burned-out factories / Out of reach but leading me / Into the
beautiful sea
 
P

Pit Capitain

2008/7/23 Shadowfirebird said:
The annoying thing is that my actual code worked "fine" when it was
all in one file but stopped working when I split the objects into
different files.

If you write everything in one file, it only works if you define class
Two before class One. If you reverse the definitions, you get the same
error. In Ruby you have to define a constant before you can use it.
This is independent of require.

If you create a file main.rb with

require "test1"
require "test2"

the code is working. If you reverse those two lines, you get the
error. This is the same behavior as in the one-file example. I suggest
you add some debugging statements in your code to understand the
sequence when loading files, for example in file test1.rb:

puts "start test1"
require "test2"
puts "before test1.One"
class One < Two
puts "in test1.One"
def self.testone(); Two.whoistwo; end
def self.whoisone(); puts "class one"; end
end
puts "end test1"

or something like this.

Regards,
Pit
 
S

Shadowfirebird

I've had my think.

I think I have some conclusions which might be useful for other people
in the same boat, and since I've not seen them anywhere else, I hope
you'll allow me to bore you with them. No doubt cooler heads than
mine will see things differently, but there you go. And, maybe I'm
missing the obvious, anyway.

1) require is mildly broken (a). It doesn't set $" until after the
given file is loaded. This can lead to errors when you have a
circular call of requires. If it set $" first, circular references
would just be ignored. Of course, you should never actually *need* to
have circular 'require' referefences, but: see my next point.

2) Require is mildly broken (b). Secondly -- and I suppose that this
is less of a bug and more of a bugbear -- there is no way to
distinguish between require-because-it-wont-load-otherwise and
require-because-some-methods-wont-run-otherwise. This second sort
isn't entirely necessary, but it's aesthetically pleasing and useful
for unit testing.

3) You can get around (1) by manually pushing the program name into $"
at the start of each file. This done, you can use a new idiom -- END
{ require } -- to specify
require-because-some-methods-wont-run-otherwise. Not the prettiest
thing, but neither the ugliest, I think: "in the end, this is
required."


Now I suppose I'd better show my working if I'm going to say anything
as contentious as all that. Apologies for the length of this, but
then, you can stop reading now. OTOH, I'd really like to hear other
points of view.

A new set of example programs:

# runner.rb
require 'root'
Root.create_data()
Root.customers.each{|c| puts c.oid, c.name}

# root.rb
require 'customer'
class Root
@@list = []

def self.create_data()
Customer.new("Dolly")
Customer.new("Reymond")
end

def self.customers()
return @@list.find_all{|x| x.kind_of?(Customer)}
end

attr_accessor :eek:id

def initialize()
@oid = @@list.size
@@list << self
end
end

# customer.rb
require 'root'
class Customer < Root
attr_accessor :name

def initialize(name)
super()
@name = name
end
end


Broadly speaking this follows the same approach as my actual code. As
listed, of course, it doesn't work: run runner.rb and it loads
root.rb, which starts by loading customer.rb, which starts by loading
root.rb, which...

I needed to make a distinction (#2 above) between code that referred
to another object when it was loaded; and code that referred to
another object when it was run. root.rb doesn't need a 'require
'customer'; runner.rb does. But this is rather unhelpful when it
comes to unit testing: when writing the test class for Root I
shouldn't have to remember to require customer. Ideally I should be
able to put the require in root.rb somehow.

For experimental purposes lets try putting the "require 'customer'" at
the *end* of root.rb. This has practical implications -- no
programmer is going to look for it there -- but it works; running
runner.rb produces no errors. Oddly enough, putting "END {require
'customer'}" at the start of the file does not work. But, wait.

What is more interesting, is that if you run root.rb or customer.rb
you get warnings about redefined methods. You shouldn't see this if
require works as described -- a file should never be called twice.
What appears to be happening is that $" is set after the file is
loaded -- that is, it's not being set until the chain of requires has
finished. We can defeat that behaviour by adding $" << "<filename>"
to the start of root.rb and customer.rb. The warnings now disappear.
(And that seems like a bug to me, or at least an undocumented feature.)

This is really odd: if we now try END{ require 'customer'} at the
start of root.rb -- well, after the $" thing -- it *works*. Really
odd, but nice.

So, root.rb now looks like this:

# root.rb
$" << "root.rb"
END { require 'customer' }
class Root
@@list = []

def self.create_data()
Customer.new("Dolly")
Customer.new("Reymond")
end

def self.customers()
return @@list.find_all{|x| x.kind_of?(Customer)}
end

attr_accessor :eek:id

def initialize()
@oid = @@list.size
@@list << self
end
end

And I can live with that. But I'm open to suggestions -- any
suggestions, except rude ones...
 

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