Scope of constants in instance_eval

P

Phrogz

I'm writing a DSL, and I want to use some constants. To be clean, I
don't want to pollute the global constant space. To be tight, I also
don't want the user to have to scope the constant using Foo::BAR, but
instead be able to use just BAR.

The following surprised me. As the scope of the block is an instance of
Foo, I had hoped/assumed that it would have access too Foo's constants.
Alas, no.

class Foo
BAR = 1
def initialize( &block )
instance_eval &block
end
def bork
puts "bork: self is #{self}"
puts "bork: BAR is #{BAR}!"
end
end

Foo.new{
bork
puts "block: self is #{self}"
puts "block: BAR is #{BAR}!"
}

#=> bork: self is #<Foo:0x32c808>
#=> bork: BAR is 1!
#=> block: self is #<Foo:0x32c808>
#=> NameError: uninitialized constant BAR


For now, I'll just shove my constants into global space before the
instance_eval, and remove them afterwards. Is there a better/cleaner
way to accomplish my goals?
 
J

Jim Freeze

--Apple-Mail-22--539250720
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed


class Foo
BAR = 1
def initialize( &block )
instance_eval &block
end
def bork
puts "bork: self is #{self}"
puts "bork: BAR is #{BAR}!"

Hmm, that could be a bug in ruby because

p BAR

works.
end
end

Foo.new{
bork
puts "block: self is #{self}"
puts "block: BAR is #{BAR}!"
}

Jim Freeze




--Apple-Mail-22--539250720--
 
P

Phrogz

Jim said:
Hmm, that could be a bug in ruby because

p BAR

works.

Hrm? Not on my machine.

class Foo
BAR = 1
def initialize( &block )
instance_eval &block
end
def bork
puts "bork: self is #{self}"
puts "bork: BAR is #{BAR}!"
end
end

Foo.new{
bork
puts "block: self is #{self}"
#puts "block: BAR is #{BAR}!"
p BAR
}

#=> bork: self is #<Foo:0x32af44>
#=> bork: BAR is 1!
#=> block: self is #<Foo:0x32af44>
#=> NameError: uninitialized constant BAR
 
P

Phrogz

For those interested in doing the same, I've hacked up a quick method
for promoting the constants of a class/module to the main Object scope,
and then restoring the original values.

You can:
* Automatically promote all constants of the class/module
* Promote only specific constants
* Choose to explicitly restore the constants or
* Supply a block that will be yielded to before the constants are
automatically restored

class Module
# Push the constants of this class/module up to global space
# saving any original values in Object for later restoration
#
# If no names are supplied, all constants in this class/module
# are promoted.
#
# If a block is supplied, it will be yielded to before automatically
# restoring the global scope to its pristine state.
def promote_constants( *const_names )
const_names.flatten!
const_names = self.constants if const_names.empty?
@_original_constant_values = {}
const_names.each { |name|
if self.const_defined?( name )
if Object.const_defined?( name )
@_original_constant_values[ name ] = Object.const_get( name )

end
Object.const_set( name, self.const_get( name ) )
else
warn "Cannot promote non-existent constant '#{name}'"
end
}
if block_given?
yield
restore_constants
end
end

# Restore Object's constants to undo the effects of
#promote_constants
def restore_constants( *const_names )
return unless @_original_constant_values
const_names.flatten!
const_names = self.constants if const_names.empty?
const_names.each { |name|
if val=@_original_constant_values[ name ]
Object.const_set( name, val )
else
Object.instance_eval{
remove_const( name )
}
end
}
@_original_constant_values = nil
end
end
 
J

Jim Freeze

Hrm? Not on my machine.

Hmm, even more interesting. Change the following:
class Foo
BAR = 1
def initialize( &block ) def initialize(str)
instance_eval &block instance_eval str
end
def bork
puts "bork: self is #{self}"
puts "bork: BAR is #{BAR}!"
end
end

Foo.new{ Foo.new %Q{
bork
puts "block: self is #{self}"
#puts "block: BAR is #{BAR}!"
p BAR
}

Now what do you get?
#=> bork: self is #<Foo:0x32af44>
#=> bork: BAR is 1!
#=> block: self is #<Foo:0x32af44>
#=> NameError: uninitialized constant BAR

Jim Freeze
 
G

George Ogata

Phrogz said:
Evaling as a string works. (Constant is resolved.)

But only in 1.9, if I understand you correctly.


g@crash:~/tmp$ cat test.rb
class C
def initialize &b
instance_eval(&b)
end
X = 2
end

C.new{puts eval('X')}
g@crash:~/tmp$ ruby -v test.rb
ruby 1.8.4 (2005-12-24) [i686-linux]
test.rb:8: (eval):1: uninitialized constant X (NameError)
from test.rb:3:in `eval'
from test.rb:8
from test.rb:3:in `initialize'
from test.rb:8
g@crash:~/tmp$ ruby19 -v test.rb
ruby 1.9.0 (2006-02-15) [i686-linux]
2
 
E

E. Saynatkari

Phrogz said:
I'm writing a DSL, and I want to use some constants. To be clean, I
don't want to pollute the global constant space. To be tight, I also
don't want the user to have to scope the constant using Foo::BAR, but
instead be able to use just BAR.

The following surprised me. As the scope of the block is an instance of
Foo, I had hoped/assumed that it would have access too Foo's constants.
Alas, no.

class Foo
BAR = 1
def initialize( &block )
instance_eval &block
end
def bork
puts "bork: self is #{self}"
puts "bork: BAR is #{BAR}!"
end
end

Foo.new{
bork
puts "block: self is #{self}"
puts "block: BAR is #{BAR}!"
}

#=> bork: self is #<Foo:0x32c808>
#=> bork: BAR is 1!
#=> block: self is #<Foo:0x32c808>
#=> NameError: uninitialized constant BAR


For now, I'll just shove my constants into global space before the
instance_eval, and remove them afterwards. Is there a better/cleaner
way to accomplish my goals?

Just a constant lookup issue for a closure. Doing
this would also work:

puts "Block: BAR is #{self.class.const_get 'BAR'}"

Though it may not be the cleanest solution :)


E
 
G

George Ogata

George Ogata said:
But only in 1.9, if I understand you correctly.

Sorry, I see this follows on from Jim's post.

Here's a silly idea that might make your other syntax work.


$bindings = []
def Object.const_missing(name)
if $bindings.empty?
raise NameError, "uninitialized constant #{name}"
end
binding = $bindings.pop
eval(name.to_s, binding)
ensure
$bindings.push(binding)
end

class C
def initialize &b
$bindings.push binding
instance_eval(&b)
ensure
$bindings.pop
end
X = 2
end

C.new{puts X}
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Scope of constants in instance_eval"

|class Foo
| BAR = 1
| def initialize( &block )
| instance_eval &block
| end
| def bork
| puts "bork: self is #{self}"
| puts "bork: BAR is #{BAR}!"
| end
|end
|
|Foo.new{
| bork
| puts "block: self is #{self}"
| puts "block: BAR is #{BAR}!"
|}
|
|#=> bork: self is #<Foo:0x32c808>
|#=> bork: BAR is 1!
|#=> block: self is #<Foo:0x32c808>
|#=> NameError: uninitialized constant BAR

In 1.8, constant uses lexical look-up, even within the block given to
instance_eval(). We changed this behavior in 1.9 to simplify things.

matz.
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top