Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Ruby, is there no way to dynamically define a local variable in the current context? [duplicate]

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.

like image 636
Peter Alfvin Avatar asked Oct 11 '13 15:10

Peter Alfvin


People also ask

How do you define a local variable in Ruby?

Local Variables: A local variable name always starts with a lowercase letter(a-z) or underscore (_). These variables are local to the code construct in which they are declared. A local variable is only accessible within the block of its initialization. Local variables are not available outside the method.

Do you have to declare variables in Ruby?

Assigning Variables Ruby is a dynamic language. Unlike strongly typed languages such as C, C++, Ruby does not require you to declare the variable's data type.

How do variables work in Ruby?

Variable names in Ruby can be created from alphanumeric characters and the underscore _ character. A variable cannot begin with a number. This makes it easier for the interpreter to distinguish a literal number from a variable. Variable names cannot begin with a capital letter.


2 Answers

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'
like image 105
dancow Avatar answered Oct 17 '22 21:10

dancow


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.

like image 23
vgoff Avatar answered Oct 17 '22 20:10

vgoff