Ruby Forgets Local Variables During a While Loop

Ruby forgets local variables during a while loop?

I think this is because message is defined inside the loop. At the end of the loop iteration "message" goes out of scope. Defining "message" outside of the loop stops the variable from going out of scope at the end of each loop iteration. So I think you have the right answer.

You could output the value of message at the beginning of each loop iteration to test whether my suggestion is correct.

How to modify loop variable in ruby?

The only way you'll be able to modify the loop variable is to use a while loop:

x = (2..7).to_a
i = 0
while (i < x.length)
i = 4 if i == 0
puts x[i]
i = i + 1
end

Output:

6
7

In a for loop, as you've discovered, you can modify the loop variable and that value will hold for that iteration of the loop. On the next iteration, it will retrieve the next element from the range you specified so your modified value will essentially be overwritten.

do loop in ruby setting the value in the block to nil

TL;DR Blocks reset their scope on each iteration. You can do return_response = nil prior to entering the loop, and it should fix your problem because it won't get reset every iteration.

This is a really interesting question because the output is somewhat misleading. I did some more research because I didn't like my answer, and this is what I came up with. (FYI, I ran all this on Ruby 2.1.1)

Variable Presence

In Ruby, variable presence in a single scope is determined by the parser. There is a really good explanation here, but for a simple example:

if false
fnord = 42
end
fnord # => nil

Contrary to this (you'll have to reload irb or otherwise reset the scope)

fnord # => NameError: undefined local variable or method `fnord' for main:Object
if false
fnord = 42
end

It's interesting behavior, and I highly recommend reading the above linked stack overflow answer for more details.

Block Scoping

Blocks in Ruby reset their local scopes every iteration.

5.times do
# This "if false" is here because otherwise the scope reset would cause x
# to be a NameError, as above. If you delete the following 3 lines it
# will raise that error when running "p x".
if false
x = 10
end
p x
x = 20
end

The x = 20 is never retained - you'll get nil printed, five times in a row, because x has been parsed (so you won't get the NameError above) but it hasn't been assigned. You could define x outside of the block scope to keep the value (or even just let the parser parse x outside the block scope, without actually doing an assignment!)

x = nil
5.times do
p x
x = 20
end

That will result in nil, then 20, 20, 20, 20. The scope of x in this case is outside the block scope (and blocks can access local variables in the scope around them). Even if you'd used the if false mechanism to "declare" x without assigning it, it would still work.

So, for your situation, the issue isn't that return_response is being scoped to the if section of the if/else statement, it's that it isn't assigned at all the second time around (but it does get parsed, so it has a value of nil, rather than raising a NameError). Let's simplify your situation a bit:

(0..1).each do |i|
if i == 0
x = 10
p x
else
p x
end
end

This prints 10, and then nil, which mirrors the problem you were having. You couldn't fix it by just "declaring" the variable prior to the if statement, but still inside the block scope (or by using block-local variables, which would do the same thing):

(0..1).each do |i|
x ||= nil # note: this is not something you'd probably do in Ruby

if i == 0
x = 10
p x
else
p x
end
end

This still prints 10, then nil. You have to move the variable outside the block scope for it to not get reset each time.

x = nil # outside the block scope
(0..1).each do |i|
if i == 0
x = 10
p x
else
p x
end
end

This will print 10, 10.

It looks like from your code you're wanting to build up the return_response from each row as you iterate. You can do return_response = nil prior to entering the loop, and it should fix your problem because it won't get reset every iteration. A more Ruby-like approach would be to assign return_response = [] prior to entering the loop and appending each record (you wouldn't need to check for i == 0 in this case). And if you didn't need the intermediate logging, you could probably get away with this

return_response = response_array.map { |response_line| response_line[:value]['value'] }.join("X-LINE-BREAK.")

I.e. map each item in the response array to its [:value]['value'], then join all those into string with "X-LINE-BREAK." in between each.

PS: These two answers also provide some information on other peculiarities with block scoping, variables, and loops, though they don't apply to your particular case: https://stackoverflow.com/a/1654665/4280232 and https://stackoverflow.com/a/21296598/4280232.

The previous answer I wrote here was incorrect, so I replaced it. If that was the wrong approach, please let me know.

Closures and for loops in Ruby

Okay, this is getting ridiculous. Every time I try to answer a question about how for loops work in Ruby, I get it wrong.

The reason for this is, of course, that I don't use for loops in Ruby, neither does anybody else, so it really doesn't matter to me :-)

Anyway, to settle the matter once and for all, I went straight to the ultimate source, the December 1, 2009 preliminary Draft of the IPA Ruby Language Specification (destined to become the ISO Ruby Language Specification):

§11.4.1.2.3 The for expression


Syntax


  • for-expression for for-variable in expression do-clause end
  • for-variable left-hand-side | multiple-left-hand-side

The expression of a for-expression shall not be a jump-expression.

Semantics


A for-expression is evaluated as follows:

  1. Evaluate the expression. Let O be the resulting value.
  2. Let E be the primary-method-invocation of the form primary-expression [no line-terminator here].each do | block-formal-argument-list | block-body end, where the value of the primary-expression is O,the block-formal-argument-list is the for-variable, the block-body is the compound-statement of the do-clause.

    Evaluate E, but skip Step c of §11.2.2.

  3. The value of the for-expression is the resulting value of the invocation.

Okay, so basically this means that

for for_variable in expression
do_clause
end

gets translated to

O = expression
O.each do |for_variable|
do_clause
end

Or, in your case:

for i in 1..5
puts j if i > 1 #undefined local variable or method `j' (NameError)
j = i
end

gets translated to

(1..5).each do |i|
puts j if i > 1 #no excpetion here, works just fine ??!!??
j = i
end

Aha! But we forgot something! There's this ominous "skip Step c of §11.2.2." thing! So, what does it say?

  • Push an empty set of local variable bindings onto ⟦local-variable-bindings⟧.

Note that Step b

  • Set the execution context to Eb.

is not skipped.

So, as far as I can see, a for loop gets its own execution context, which starts out as a copy of the current execution context, but it does not get its own set of local variable bindings. IOW: it gets its own dynamic execution context, but not its own lexical scope.

I must admit, I'm still not sure I fully understand it, but it doesn't get any more precise than this.

How do I dynamically create a local variable in Ruby?

You have to use the correct binding. In IRB for example this would work:

irb(main):001:0> eval "t=2", IRB.conf[:MAIN_CONTEXT].workspace.binding
=> 2
irb(main):002:0> local_variables
=> [:t, :_]
irb(main):003:0> eval "t"
=> 2
irb(main):004:0> t
=> 2

Mysterious error with beginner's ruby code (while loops)

Here is the fixed code :

def while_var(x)
i = 0
numbers = []
while i < x
print "Entry #{i}: i is now #{i}."
numbers.push(i)
puts "The numbers array is now #{numbers}."
i = i + 1
puts "variable i just increased by 1. It is now #{i}."
end
end

You did several mistakes :

  • You forgot to close the while loop.
  • You used #{x} which is not correct syntax for interpolation,But you don't need interpolation here. make it only x.
  • Inside the method two local variables i and numbers can't be used,as they have created at the top level. So you need to create those variables locally inside the method.

Using loop counter as part of a variable's name in Ruby

As others have pointed out it is not possible to create local variables dynamically in Ruby, you could set up a binding as well if you're looking for another method of achieving this.

With eval

b = binding
10.times do |i|
eval("var#{i} = 'foo'", b)
end

> eval("var1", b)
=> "foo"

> eval("local_variables", b)
=> [:var9, :var8, :var7, :var6, :var5, :var4, :var3, :var2, :var1, :var0, :b, :_]

Without eval

b = binding
10.times do |i|
b.local_variable_set("var#{i}", 'foo')
end

> b.local_variable_get('var1')
=> "foo"

> b.local_variables
=> [:var9, :var8, :var7, :var6, :var5, :var4, :var3, :var2, :var1, :var0, :b, :_]

Ruby: why do while and until not return last line they execute from a function?

In both your examples, the while loop results as nil.

From while loop:

The result of a while loop is nil unless break is used to supply a value.

The same for until:

Like a while loop the result of an until loop is nil unless break is used.

ruby inline while vs while end

That is, in general, an interpreter problem, that could not appear in languages with local variable bubbling, like javascript.

The interpreter (reading from left to right) meets right-hand-operand n before any mention of it.

The more I think about it, the more I am convinced it is a bug in ruby interpreter. As @Cary pointed out, the control flow is in fact the same:

a = [2, 3]
n = 1
puts n while n = a.shift
#⇒ 2
#⇒ 3

No trail of 1 in the output above.

Variable scope and order of parsing vs. operations: Assignment in an if

It only happens when you try to assign a literal value, if you call a function it works.

def foo(a)
a
end

p 'not shown' if(value = foo(false))
p 'shown' if(value = foo(true))

# This outputs a Warning in IRB
p 'shown' if(value = false)
(irb):2: warning: found = in conditional, should be ==

If you turn on debugging (-d) you will see a warning about an used variable value

warning: assigned but unused variable - value

This "works" because the statement does evaluate to true as far as if is concerned, allowing the code preceeding it to run.

What is happening here is that if() when used as a modifier has it's own binding scope, or context. So the assignment is never seen outside of the if, and therefore makes no sense to perform. This is different than if the control structure because the block that the if statement takes is also within the same scope as the assignment, whereas the line that preceeded the if modifier is not within the scope of the if.

In other words, these are not equivelant.

if a = some(value)
puts a
end

puts a if(a = some(value))

The former having puts a within the scope of the if, the latter having puts a outside the scope, and therefore having different bindings(what ruby calls context).

Ruby Order of Operations



Related Topics



Leave a reply



Submit