Scope of a local variable in a block
Asked Answered
S

6

8

If I have:

2.times do
  i ||= 1
  print "#{i} "
  i += 1
  print "#{i} "
end

I get 1 2 1 2, whereas I was expecting 1 2 2 3. Why does i lose its assignment when the loop starts over? It behaves as expected if the assignment occurs outside the loop, so I guess it has to do with scope, but I didn't realize loops have their own scopes. Can someone clarify?

Update: Thanks for the help on this. Part of my confusion stemmed from coming to Ruby from Python, which doesn't have block scope (I think).

Shipping answered 15/5, 2013 at 17:48 Comment(1)
is this for pedagogic purposes? because this kind of code is utterly unidiomatic...Galore
C
9

Look at the code below:

2.times do
  p defined? i
  i ||= 1
  p defined? i
  p "#{i} "
  i += 1
  p "#{i} "
end

Output:

nil 
"local-variable"
"1 "
"2 "
nil
"local-variable"
"1 "
"2 "

That means in each iteration a new scope is created,and i is known to that scope only; which is proved by nil and "local-variable".

Now i is created outside of block, and see the output(no nil comes):

i = nil
2.times do
  p defined? i
  i ||= 1
  p defined? i
  p "#{i} "
  i += 1
  p "#{i} "
end

Output:

"local-variable"
"local-variable"
"1 "
"2 "
"local-variable"
"local-variable"
"2 "
"3 "

To know more about ||= look What Ruby’s ||= (Double Pipe / Or Equals) Really Does

Cayes answered 15/5, 2013 at 17:53 Comment(0)
A
10

I don't know what your expectations are based on. If you think what I think you think, it should be 1 2 2 3. You can achieve that by declaring variable i outside of the block.

i = nil

2.times do
  i ||= 1
  print "#{i} "
  i += 1
  print "#{i} "
end

Then the block closes over that variable (closure) and uses it. Without closure, i is local to the block and is new every time.

Apthorp answered 15/5, 2013 at 17:51 Comment(0)
C
9

Look at the code below:

2.times do
  p defined? i
  i ||= 1
  p defined? i
  p "#{i} "
  i += 1
  p "#{i} "
end

Output:

nil 
"local-variable"
"1 "
"2 "
nil
"local-variable"
"1 "
"2 "

That means in each iteration a new scope is created,and i is known to that scope only; which is proved by nil and "local-variable".

Now i is created outside of block, and see the output(no nil comes):

i = nil
2.times do
  p defined? i
  i ||= 1
  p defined? i
  p "#{i} "
  i += 1
  p "#{i} "
end

Output:

"local-variable"
"local-variable"
"1 "
"2 "
"local-variable"
"local-variable"
"2 "
"3 "

To know more about ||= look What Ruby’s ||= (Double Pipe / Or Equals) Really Does

Cayes answered 15/5, 2013 at 17:53 Comment(0)
P
3

It isn't the "loop" that has a scope. It's the block. Yes, a block is a local scope.

If you do not want a variable to be understood as local to the block, it needs to exist outside the block beforehand. Even just setting i to nil in a preceding line would do this.

(But your expectation of 1 2 3 4 will still not quite be met...!)

Palacios answered 15/5, 2013 at 17:50 Comment(1)
Actually, even just if false then i = nil end would be enough, but exactly why that is so would probably be too confusing to explain in this context.Tomtom
D
2

You can have some fun with it. Say for example you want to access the scope inside the block.

block = -> do
  x = "Hello from inside a block"
  binding # return the binding
end

p defined? x              #=> nil
x = eval "x", block.call  #=> #<Binding:0x007fce799c7dc8>
p defined? x              #=> "local-variable"
p x                       #=> "Hello from inside a block"

This is important because it allows developers to basically blow away the encapsulation of a block, and should be used with caution.

Dupion answered 15/5, 2013 at 20:17 Comment(0)
N
1

Simple answer is that you reinstanciate the variable i on each iteration and being reset to the value of one.

Noninterference answered 15/5, 2013 at 20:46 Comment(0)
C
0

Ruby loop like x.times create local scope so variables introduce within this loop are local and will be destroyed after reaching the end of block. That's why you are not getting your expected result. In ruby if you want loop with out local scoping you can use for or while loop that does not create local scope but your local variable i will remain accessible after loop.

for j in (1..2)
  i ||= 1
  print "#{i} "
  i += 1
  print "#{i} "
end

print 1 2 2 3 as your expected result. Now if we run above loop aging it will give the result 3 4 4 5. For extra information while and for work as same (scope less & print 1 2 2 3) on the other hand each, x.upto(y) and x.times work as same (create local scope and print 1 2 1 2)

Chirography answered 10/9, 2015 at 8:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.