[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Why don't I see these module constants?

Lloyd Zusman

2/20/2005 5:17:00 AM

Suppose I have a file named 'modtest' which contains the following two
lines:

X = 1
Y = 2

Then, I execute this ruby code:

mod = Module.new {
load('modtest')
}
p mod.constants

When this code runs, I see this: []

In other words, it's an empty array, which means that I'm not seeing the
constants X and Y.

Why is this? Is there any way to load the file 'modtest' so that the
resulting anonymous module indeed contains these constants?

Thanks in advance.

--
Lloyd Zusman
ljz@asfast.com
God bless you.



5 Answers

Joel VanderWerf

2/20/2005 8:32:00 AM

0

Lloyd Zusman wrote:
> Suppose I have a file named 'modtest' which contains the following two
> lines:
>
> X = 1
> Y = 2
>
> Then, I execute this ruby code:
>
> mod = Module.new {
> load('modtest')
> }
> p mod.constants
>
> When this code runs, I see this: []
>
> In other words, it's an empty array, which means that I'm not seeing the
> constants X and Y.
>
> Why is this? Is there any way to load the file 'modtest' so that the
> resulting anonymous module indeed contains these constants?

It's not particularly loading that's causing the problem. The same
behavior occurs in

mod = Module.new {
XYZ = 1
}
p mod.constants

We can find out where that constant goes with this bit of code:

ObjectSpace.each_object(Module) do |obj|
if obj.constants.include?("XYZ")
puts "#{obj}::XYZ = #{obj::XYZ.inspect}"
end
end

The output:

[]
t.rb:8: warning: toplevel constant XYZ referenced by Class::XYZ
Class::XYZ = 1
t.rb:8: warning: toplevel constant XYZ referenced by Module::XYZ
Module::XYZ = 1
Object::XYZ = 1

The output says XYZ is toplevel. The constant is added in the top level
scope because that is the scope of the block in which the constant
assignment happens. Here's another example:

module Foo; end

module Bar
$block = proc { XYZ = 1 }
end

Foo.module_eval(&$block)

p Bar::XYZ # ==> 1
p Foo::XYZ # ==> uninitialized constant

Looks weird at first, but it's because constants are statically
(lexically) scoped. XYZ occurs in a block whose scope is Bar, so that's
where the constant is defined when the block is called.

You can get around this by using the string form of module_eval. A
string is not associated with its lexical scope in the way a proc is.

mod2 = Module.new
mod2.module_eval "X2 = 12"
p mod2.constants # ==> ["X2"]

So just use #read instead of #load, and pass that string to module_eval.


Pit Capitain

2/20/2005 10:56:00 AM

0

Lloyd Zusman schrieb:
> Suppose I have a file named 'modtest' which contains the following two
> lines:
>
> X = 1
> Y = 2
>
> Then, I execute this ruby code:
>
> mod = Module.new {
> load('modtest')
> }
> p mod.constants
>
> When this code runs, I see this: []
>
> In other words, it's an empty array, which means that I'm not seeing the
> constants X and Y.

In addition to what Joel already said, note that

mod = Module.new {
load('modtest')
}

is the same as

mod = Module.new
load('modtest')

Kernel#load doesn't load the code in the context where it is called, but in the
context of the toplevel object (main) or in the context of an anonymous module,
if you set the second parameter to true. You can test this if you put a "p self"
into the file modtest.rb.

Regards,
Pit


Lloyd Zusman

2/20/2005 11:17:00 AM

0

Joel VanderWerf <vjoel@PATH.Berkeley.EDU> writes:

> [ ... ]
>
> The output says XYZ is toplevel. The constant is added in the top level
> scope because that is the scope of the block in which the constant
> assignment happens. Here's another example:
>
> module Foo; end
>
> module Bar
> $block = proc { XYZ = 1 }
> end
>
> Foo.module_eval(&$block)
>
> p Bar::XYZ # ==> 1
> p Foo::XYZ # ==> uninitialized constant
>
> Looks weird at first, but it's because constants are statically
> (lexically) scoped. XYZ occurs in a block whose scope is Bar, so that's
> where the constant is defined when the block is called.

Thanks. I get it. It indeed seems counter-intuitive, but if that's the
way that constants are scoped in ruby, then so be it.


> [ ... ]
>
> So just use #read instead of #load, and pass that string to module_eval.

Yep. That works fine. Thanks.


--
Lloyd Zusman
ljz@asfast.com
God bless you.



Lloyd Zusman

2/20/2005 11:32:00 AM

0

Pit Capitain <pit@capitain.de> writes:

> [ ... ]
>
> In addition to what Joel already said, note that
>
> mod = Module.new {
> load('modtest')
> }
>
> is the same as
>
> mod = Module.new
> load('modtest')
>
> Kernel#load doesn't load the code in the context where it is called, but
> in the context of the toplevel object (main) or in the context of an
> anonymous module, if you set the second parameter to true. You can test
> this if you put a "p self" into the file modtest.rb.

Yes, I see. Thanks.

I think that it would be nice if in a future ruby release, the
two-argument form of load() would return the anonymous module. Then,
with the following modtest file ...

X = 1
Y = 2

... I could then do this:

mod = load(modtest, true)
p mod.constants

... which should print this: ["X", "Y"]

The 'mod' return value would still be considered 'true', which is what
that call returns today if the load succeeds. In that case, we could at
least access that anonymous module if we wish.

Or here's another possibility. The second argument would get returned
to the caller, and if that argument was a module, the load would take
place within its context; otherwise, the load would just take place in
an anonymous module like today:

mod0 = Module.new
mod1 = load(modtest, mod0)
p mod0
p mod1

In this case, the load could take place within the mod0 module, which
would be returned; therefore, mod0 and mod1 would be equivalent after
this call. This would then print ["X", "Y"] twice.

Thoughts?

--
Lloyd Zusman
ljz@asfast.com
God bless you.



Joel VanderWerf

2/20/2005 8:36:00 PM

0

Lloyd Zusman wrote:
> I think that it would be nice if in a future ruby release, the
> two-argument form of load() would return the anonymous module. Then,

Ruby being ruby, it's easy enough to add a feature like this.

---------
class Module
def load_in_module(file)
module_eval(IO.read(file), File.expand_path(file))
end
end

m = Module.new
m.load_in_module("some-file.rb")
p m::X
---------

where some-file.rb is:
---------
X = 1
---------

I wrapped this up in a little library called "script" (a little small
for RAA, but it's there), which adds a few other niceties:

* instantiates the module and loads the file in one method call (whoopdeedo)
* defines #require so that the loaded file can require files relative to
its own directory
* makes all methods defined in the file "module_functions" so that they
are available in the form M.foo
* autoload equivalent
* can pass input to loaded files:
script = Script.load("my-script.rb") { |script| script::INPUT = 3 }