3

(I've only been coding for a few weeks and this is my first question here, so please bear with me!)

In ruby, I know that you can initialize multiple variables on a single line like this:

a, b = 1, 2

However, I am wondering if it is possible to initialize multiple variables in a loop that also generates their names. Here's some pseudocode that explains what I mean:

For X between 0 and 3, even_X = X * 2

This would set even_0 == 0, even_1 == 2, even_2 == 4, and even_3 == 6.

I realize that one can achieve the same functionality by iteratively creating an array and then calling on its members, but I am still curious if there is a way to do this.

Thanks!

hoffm
  • 2,386
  • 23
  • 36
  • 1
    While it is possible to generate variable names on the fly in many languages, in general that is discouraged because it can be a maintenance and debugging nightmare. – the Tin Man Aug 06 '11 at 17:44
  • @the Tin Man: Well, `instance_variable_set` is sometimes really useful - only not in this context. – Mladen Jablanović Aug 06 '11 at 18:23

3 Answers3

6

There is a way, using eval, but you would rather not want to use it (and I would even go that far to say that it might be better not to learn it until well later).

There is simply no case when you would use that instead of plain arrays.

For your example, one should use class Range and method map:

(0..3).map{|i| i * 2}
#=> [0, 2, 4, 6]

You can see that this has been done without declaring any variable - even i is alive just within the block passed to map. It doesn't exist afterwards.

Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
4

If you're using Ruby 1.8 you would be able to use eval to do what you're after. However if you're using 1.9 it won't work, since in 1.9 local variables created in an eval can not be accessed outside of the eval.

So, to answer your question, no it is not possible to dynamically create local variables like you've specified in your pseudocode. However, we can do what you're after if we're ok with creating instance variables. So you could do something along the lines of:

4.times do |i|
  self.instance_variable_set("@even_#{i}".to_sym, i*2)
end

This will do what you want but you will get a bunch of instance variables instead of local variables.

You could also dynamically define accessor and mutator methods using instance_eval and either attr_accessor or define_method, but the end result will be the same - you will dynamically create a bunch of instance variables.

skorks
  • 4,376
  • 18
  • 31
  • Thanks for this informative response, skorks. Give, Mladen's comment above, I suppose that my next question is: when, if ever, would you use this kind of construction? – hoffm Aug 06 '11 at 17:28
  • Metaprogramming constructs like this are the things that make ruby so powerful. If you ever study the source of Rails or Sinatra or many other well known open source projects, you will see this stuff everywhere. For a trivial example such as the one above it is certainly tremendous overkill and Mladen's answer is by far a better choice. But that example IS very trivial, in the wild you will meet much more complex problems. I can give a very specific example of that exact construct from a project that I wrote (I'll continue in the next comment since I am running out of chats here :)). – skorks Aug 07 '11 at 05:22
  • Have a look at https://github.com/skorks/attest/blob/master/lib/attest/execution_context.rb on line 18. Looks very similar to our trivial example doesn't it? The short explanation of it is this. I needed to make sure that all instance variables which were initialized in "before" blocks (which were executed in a different context), were available in the current context. Since these variables could be anything, I copy everything across from one context to another using instance_variable_set/get. – skorks Aug 07 '11 at 05:28
  • If you've only been coding for a little while, you should know that this is advanced stuff, so I wouldn't worry too much about it at this stage. Get your basics down and get some experience under your belt, you'll get to it eventually especially now that you know what's possible. The Metaprogramming Ruby book has a lot more on this kind of stuff, so keep it in mind. – skorks Aug 07 '11 at 05:36
  • Thanks, @skorks! I'll take a look at that example. Of course, the actual problem I'm facing is more complex than the example I created to ask my question. Nevertheless, I do think that the metaprogramming solution would be overkill here. I can definitely make do with iterating through arrays. I do look forward to getting my feet wet with some metaprogramming sometime soon, though. – hoffm Aug 07 '11 at 20:26
1

I completely agree with Mladen. From a conceptual point of view, you're almost always better off using methods (furthermore: dynamically defining local variables is very hard to understand for someone else reading your code):

def even(x)
   x * 2
end

# from here on the power of the computing universe is at your hands:
even(5050)
#=> 10100
Matt
  • 17,290
  • 7
  • 57
  • 71