Forum: Ruby Scope of constants in instance_eval

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Phrogz (Guest)
on 2006-02-26 19:25
(Received via mailing list)
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?
Jim F. (Guest)
on 2006-02-26 20:00
(Received via mailing list)
On Feb 26, 2006, at 11:23 AM, Phrogz wrote:

> 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 F.
Phrogz (Guest)
on 2006-02-26 20:09
(Received via mailing list)
Jim F. wrote:
> 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
Phrogz (Guest)
on 2006-02-26 20:18
(Received via mailing list)
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
Jim F. (Guest)
on 2006-02-26 20:34
(Received via mailing list)
On Feb 26, 2006, at 12:08 PM, Phrogz wrote:

> Jim F. wrote:
>> Hmm, that could be a bug in ruby because
>>
>>     p BAR
>>
>> works.
>
> 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 F.
Phrogz (Guest)
on 2006-02-26 21:40
(Received via mailing list)
Evaling as a string works. (Constant is resolved.)
E. Saynatkari (Guest)
on 2006-02-27 00:12
Phrogz wrote:
> 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
George O. (Guest)
on 2006-02-27 00:39
(Received via mailing list)
"Phrogz" <removed_email_address@domain.invalid> writes:

> 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
George O. (Guest)
on 2006-02-27 01:10
(Received via mailing list)
George O. <removed_email_address@domain.invalid> writes:

> "Phrogz" <removed_email_address@domain.invalid> writes:
>
>> Evaling as a string works. (Constant is resolved.)
>
> 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}
Yukihiro M. (Guest)
on 2006-02-27 01:49
(Received via mailing list)
Hi,

In message "Re: Scope of constants in instance_eval"
    on Mon, 27 Feb 2006 02:23:34 +0900, "Phrogz" 
<removed_email_address@domain.invalid>
writes:

|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.
This topic is locked and can not be replied to.