Reputation: 111
I assign the value "1" to the variables x and y outside the block, and then I reference the variable x of the same name outside the block in a block, so x is both 1.
Next, I assign the value of x to y in the block. In theory, y should also be 1, but the most trusted result is x = 1, y = 3.
Please tell me why.
x = 1
y = 1
ary = [1, 2, 3]
ary.each do |x|
y = x
end
p [x, y]
Upvotes: 1
Views: 196
Reputation: 4956
Because you are redefining x
in the scope of the block. ary.each do |x|
does this.
Upvotes: 2
Reputation: 80065
Block parameters (the stuff between the pipes like |x,y|
) are local to the block. But pre-existing variables are not.
total = 0
[1,5].each{|n| total += n} #works because total starts out as 0
puts total # => 6
If you want a block-local total
then follow the parameters by a semicolon and a comma-separated list of variables
total = 0
[1,5].each{|n; total, _unused| total += n} #Error because total is nil
puts total
Upvotes: 3
Reputation: 44695
The source of confusion here is so-called shadowing. You have two local variables called x
here, but only one called y
. The second x
variable is shadowing the first one - meaning that any attempt to access or assign to the first variable is doomed to fail.
When you do ary.each do |x|
, you are creating new variable, which has nothing (except for the name) to do with the variable x
you have created in the outer scope.
y
on the other hand is not shadowed and the loop binding is accessing (and assigning to) the variable defined in the parent scope. Unfortunately there is no way (at least that I am aware of) of explicit local variable creation like var
/let
/const
in javascript.
In short, your code executes as follow:
x = 1
y = 1
ary = [1, 2, 3]
ary.each do |x2|
y = x2
end
p [x, y]
So in fact, assignment to y works just as expected. You assign it value of 1 before the block, and then you assign 3 more times within the block. The last assignment is y=3
so that's its final value.
BEWARE, THE HACK:
Well there actually is a way of forcing the local variable creation, and it is to add an extra yielding argument to each block. This should never be done in the actual code, and I only present it for completeness. You should actually avoid any variable shadowing whenever possible.
x = y = 1
ary = [2,3,4]
ary.each do |x, y| # this creates new local variables x and y, y is always set to nil for each iteration.
y = x # y here is a shadowing variable, not the y of the parent scope
puts [x,y].inspect
end
puts [x,y].inspect
# OUTPUTS:
[2, 2]
[3, 3]
[4, 4]
[1, 1]
Upvotes: 3