2

I'd like to write the method (define_variables) which can get a block and use the variables defined in it. Is it possible? For example, I'd like to get 5 in output:

module A
  def self.define_variables
    yield
    puts a # not 5 :(
  end
end

A::define_variables do
  a = 5
end

Maybe there is some tricks with eval, but haven't found anyone yet.

evfwcqcg
  • 15,755
  • 15
  • 55
  • 72
  • 3
    What's the actual use case for this? It seems quite backwards to me. – Andrew Marshall Feb 12 '12 at 02:42
  • 1
    If you wanted to make use of some calculation that is made within the block that is passed, your best bet is to return something from that block and do something with it. It doesn't make a lot of sense to rely on the naming of local variables within the proc that is passed. What's to guarantee that someone uses "a" as a variable? How would you prevent an arbitrary proc from stomping your own variables when you didn't want to? – Marc Talbot Feb 12 '12 at 04:24
  • I was trying to make a `GameWorld` module for simple console game, this module has `load` and `run` methods. load method to load game_objects and store them in array, `run` to describe some interaction between them. In GameWorld's scope I can access the game_objects array and output some information (health, score, etc). Just for "symmetry" and learning purposes I was trying to use blocks with both methods. For now I load objects directly into the module's constant called `game_objects`. Anyway, thanks a lot for help, now it's getting more clear how scopes and blocks work. – evfwcqcg Feb 12 '12 at 04:54

2 Answers2

5

In short, no. After you've called yield those variables defined in the block are gone (sort of, as we shall see), except for what is returned—that's just how scope works. In your example, the 5 is still there in that it is returned by the block, and thus puts yield would print 5. Using this you could return a hash from the block {:a => 5}, and then access multiple "variables" that way. In Ruby 1.8 (in IRb only) you can do:

eval "a = 5"
a # => 5

Though I don't know of anyway to eval the contents of a block. Regardless, in Ruby 1.9 the scope of eval was isolated and this will give you a NameError. You can do an eval within the context of a Binding though:

def foo
  b = yield
  eval(a, b) + 2
end

foo do
  a = 5
  binding
end # => 7

It seems to me that what you're trying to do is emulate macros in Ruby, which is just not possible (at least not pure Ruby), and I discourage the use of any of the "workarounds" I've mentioned above.

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
2

Agreed that this is a bit backwards, and Andrew's explanation is correct. If your use case is defining variables, however, there are already class_variable_set and instance_variable_set methods that are great for this:

module A
  def self.define_variables(vars = {})
    vars.each { |n, v| class_variable_set n, v }
    puts @@a
  end
end

A::define_variables :@@a => 5

The above is more of an example of how it would work within the code you've posted rather than a recommendation.

brymck
  • 7,555
  • 28
  • 31