10

I'm wondering if there is a method which will allow me to dynamically define a previously undefined variable in the current context. For example:

foo # => NameError: undefined method or local variable ...
# Some method call which sets foo = 1 in the local context
foo # => 1

Put another way, given that foo is undefined, I'm looking for any code that would let me define the local variable foo without using the foo variable (e.g. if I had some other variable bar whose value was :foo and I had to rely on that to set the value of foo).

It seems that eval('foo = 1') or eval('foo = 1', binding) or, in Ruby 2.1, binding.local_variable_set(:foo, 1) are all equivalent to:

1.times do
  foo = 1
end

in other words, they set foo in the context of a new local context, such that the value is inaccessible outside of that context.

Is what I'm looking to do possible?

Update: This question is not specific to any particular local variable context (module/class, method, proc, block, etc.). I'd be interested in knowing definitively any context where it can or cannot be done.

Peter Alfvin
  • 28,599
  • 8
  • 68
  • 106
  • what is the problem that you are trying to solve? what ever you are trying to do, it's probably possible in ruby, but i don't think that this is a good thing to do. – phoet Oct 11 '13 at 15:39
  • 1
    Not the same, but perhaps `define_method(:foo) { 1 }` is an option? – spickermann Oct 11 '13 at 15:50
  • 1
    I know it involves variable scope, but I don't understand your question. – icantbecool Oct 11 '13 at 15:50
  • @phoet I'm asking about the Ruby language independent of any need. The question arose in the context of another SO question. – Peter Alfvin Oct 11 '13 at 16:35
  • @spickermann I agree that would be similar, but not the same. :-) – Peter Alfvin Oct 11 '13 at 16:36
  • @icantbecool Not sure if it will help, but I updated the question to include another way of stating it. – Peter Alfvin Oct 11 '13 at 16:56
  • RE your comment to my answer. Presumably, the answer could be "'yes', all contexts", "'yes' in some but contexts, 'no' in others" or "never!'. It might be helpful for readers to try to narrow the possible answers by attempting to prove or disprove the assertion that a local variable can be added in a particular context, such as to a class, method and block. – Cary Swoveland Oct 11 '13 at 20:20
  • @CarySwoveland Ok, I'll update the question. – Peter Alfvin Oct 11 '13 at 20:21
  • Since a block is not an object, I think we can safely rule that one out. More generally, I'd think that if it were possible for any object, we'd have object methods `local_variable_set` and `.._get` (sic), to go along with `instance_variable_set` and `.._get`. – Cary Swoveland Oct 11 '13 at 20:39
  • @CarySwoveland I don't see how a block not being an object has anything to do with the ability to create a local variable within a block. Given that `Kernel#binding` purportedly returns the current context, that's really all you need to reference the current context. – Peter Alfvin Oct 11 '13 at 20:42
  • "The [Kernel module](http://www.ruby-doc.org/core-2.0.0/Kernel.html) is included by class Object, so its methods are available in every Ruby object.". – Cary Swoveland Oct 11 '13 at 20:48
  • @CarySwoveland Right, that's my point. :-) I was responding to your comment that we could rule out "blocks" because they weren't objects. And in Ruby 2.1, there _is_ a `local_variable_set`, but it behaves just like `eval`, per http://ruby-doc.org/core-trunk/Binding.html#method-i-local_variable_set – Peter Alfvin Oct 11 '13 at 20:55
  • Very interesting. Didn't know about those being in 2.1. Good choice of names. A couple of uses of `Binding#local_variable_get` and `_set` are given in the ruby-truck [documentation](https://www.ruby-forum.com/topic/4416267) for those methods. – Cary Swoveland Oct 11 '13 at 21:19

3 Answers3

10

It seems that Ruby's magic would provide a way, but according to Matz, this was only possible in 1.8 via eval and only in certain contexts (i.e. irb). As of 1.9, this behavior was taken out ("strictly forbidden"):

Matz himself weighs in here: https://www.ruby-forum.com/topic/155673#685906

I read from somewhere that now Ruby can't dynamically create local variable. Is it true or just a bug?

The local variables are created in compile time, so that local variables that are defined in eval() cannot be accessed outside of eval. In 1.8, irb and tryruby does line by line compilation so that local variables are spilled from eval(), but in 1.9, it's strictly prohibited even under line-by-line compilation.

          matz.

(Non-sequitur alternative here, for anyone who wants something like this but not the exact technical situation that the questioner has):

Use a hash:

local_hash = {}

my_vars.each_pair do |k,v|
   local_hash[k] = v
end

puts local_hash['foo']
#=> 'baz'
dancow
  • 3,228
  • 2
  • 26
  • 28
  • If you'll update your answer to simply say "No, it cannot be done" and provide the Matz reference, I'll be glad to accept it. This assumes that in addition to it not being possible with `eval`, there is no other method to accomplish it either in light of Matz's explanation about local variables only being created at compile time. – Peter Alfvin Oct 11 '13 at 18:23
  • 1
    Unfortunately I can't do that...I only know of Matz's explanation but can't tell you for sure that just because it's been stopped via `eval` that it can't be done via another method (other than running Ruby 1.8). So I'm OK with leaving the door open rather than being proven wrong by a master Ruby hacker :) – dancow Oct 11 '13 at 18:57
  • Ok, how about at least removing the "I'm sure there's a way" comment, in which case I'll +1 for the Matz reference. :-) While they're _may_ be a way, I don't think there's any reason for you to "be sure" and I'd hate to give anyone false hope. On a related point, as evidenced in my comments on the question, I'm not trying to solve any underlying problem; I'm just trying to improve my understanding of the language and the core libraries. As such, your proposal involving the Hash is not really relevant and, from my point of view, would best be removed. – Peter Alfvin Oct 11 '13 at 19:08
  • 1
    Done. But I hope someone proves me wrong and comes up with some clever hack, though I can't imagine what that could possibly be, given Matz's explanation – dancow Oct 11 '13 at 22:46
  • Accepted and plus 1. :-) – Peter Alfvin Oct 11 '13 at 22:55
  • @PeterAlfvin The problem with this mental exercise is that you restrict the solution to the technique instead of discovering a technique that provides the solution. If you had been trying to solve a real problem instead, the hash solution would be perfectly acceptable. The point here is to discover a technique that solves the problem and that is what it does. – Richard_G Jun 11 '15 at 14:50
  • @R_G If I had been trying to solve a real problem, then I would have posted that real problem. My interest was solely in this particular aspect of the language and in that context, the context of the question, any other technique for storing and retrieving values is, as Dan noted, a non-sequitur. – Peter Alfvin Jun 12 '15 at 05:19
2

In the context of creating the local variable itself, it is true there are some difficulties to overcome, however assigning dynamically is still no problem.

>> my_lv = 0
=> 0
>> instance_eval("#{'my_lv'} = 42")
=> 42
>> my_lv
=> 42

So, simply create from a gathered input (from gets, chomped or stripped as needed, it will just naturally end up as a string) and call to_sym on it and stuff the new symbol into local_variables and eval away...

>> local_variables << :my_created_lv
=> [:my_lv,
 :__,
 :_,
 :_dir_,
 :_file_,
 :_ex_,
 :_pry_,
 :_out_,
 :_in_,
 :my_created_lv]
>> 

Then you take the gathered string that you converted to a symbol, and assigned to in the code shown above, and eval it to get the value.

>> eval :my_lv.to_s
>> 24

As noted in another answer, I am unable to easily replicate this outside of Pry or IRB.

This has changed in future versions of Ruby, as Matz has removed and works hard to make this no longer able to happen.

vgoff
  • 10,980
  • 3
  • 38
  • 56
  • I'm asking about the case where the variable is not already defined. There's no issue where the variable is already defined and you just want to change the value. – Peter Alfvin Oct 11 '13 at 21:35
  • That is why I used a string inside the interpolation. The rest is academic. – vgoff Oct 11 '13 at 21:38
  • And only works in a REPL apparently. :( – vgoff Oct 11 '13 at 23:16
  • Sorry, I didn't follow either of your last two comments. – Peter Alfvin Oct 11 '13 at 23:19
  • I took the commands I wrote in the answer and wrote a normal Ruby program to attempt to verify that it still works as a normal Ruby program, rather than line by line evaluation, and it is restricted from working in a regular Ruby program. REPL means "Read - Eval - Print - Loop". The prior was just meant to say that the string inside of the interpolation could have been received via a number of ways, `gets.chomp` perhaps. Either way, not going to be easily defeated in with Ruby 1.9 and later. – vgoff Oct 11 '13 at 23:33
  • same as `binding.eval` and cannot declare a non-existing variable – akostadinov Sep 08 '14 at 12:58
0

Would a class instance variable work for you?

class Cat
end
Cat.instance_variable_set '@last_words', 'meow, meow, me...'
Cat.instance_variable_get '@last_words' # => "meow, meow, me..."
Cat.new.instance_variable_get '@last_words' # => nil

If not, please elaborate on the context and how you would use the local variable.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    My question is independent of where the local variable resides (class, method, block, whatever). And per my earlier comment on the question itself, there is no underlying problem I'm trying to solve. I'm merely asking a question about the language and core libraries related to local variable declaration in order to better my understanding. – Peter Alfvin Oct 11 '13 at 20:08