Block scope in ruby
Solution 1
This is expected behaviour for ruby 1.8. It was fixed in 1.9. Snippets below are run with ruby 1.9.3
food = ['toast', 'cheese', 'wine']
food.each { |food| puts food.capitalize.inspect} # !> shadowing outer local variable - food
puts food.inspect
# >> "Toast"
# >> "Cheese"
# >> "Wine"
# >> ["toast", "cheese", "wine"]
You are correct, food
from the block is scoped to that block and shadows other variables with this name. But if you do something destructive to it, it will be reflected in the original array, because it is reference to array element, not its copy. Observe:
food = ['toast', 'cheese', 'wine']
food.each { |f| f.capitalize} # transform and discard
food # => ["toast", "cheese", "wine"]
food.each { |f| f.capitalize! } # transform destructively (bang-version)
food # => ["Toast", "Cheese", "Wine"]
Solution 2
The block inherits the scope from the context it is defined in. Take a look at this example:
def give_me_a_proc
test = "foo"
lambda { puts test }
end
test = "bar"
give_me_a_proc.call
# => "foo"
So it doesn't matter where the proc/block is executed, but rather where it was defined.
In your case the food
variable inside the block is indeed shadowing the food
array from outside. But you really operate on the actual elements (not duplicates/clones) of food
array in the block so any changes to them will be reflected outside.
The reason why food
array changes into "Wine"
string after the block is finished is that the block operates in the same scope the food
array was defined, so it overwrites it with the last element of the array since it is the last object that is assigned to food
variable.
Solution 3
This bug has been fixed in Ruby 1.9
food = ['toast', 'cheese', 'wine']
food.each { |food| puts food.capitalize }
puts food # ["toast", "cheese", "wine"]
I think if you want to use local variable in Ruby 1.8, try lambda
(Sorry, I don't install Ruby 1.8, so can't test it, but work in 1.9)
food = ['toast', 'cheese', 'wine']
lambda do |food|
food.each {|food| puts food.capitalize}
end.call(food)
puts food # ["toast", "cheese", "wine"]
Related videos on Youtube
Comments
-
18bytes almost 2 years
My understanding was that ruby blocks have block scope, and all variables created inside block will live only within the block.
Example case:
food = ['toast', 'cheese', 'wine'] food.each { |food| puts food.capitalize} puts food
Output:
"Toast" "Cheese" "Wine" "Wine"
If you take the
food
variable inside the block (Each block), my understanding was that it has block scope. It lives only within the block scope, and does not have any influence on the outer variablefood
.But the behavior is different, the outer variable named
food
is modified in this case. Is this understanding correct, In ruby do we have block scope? -
18bytes over 11 yearsI use ruby version 1.8.7. I do not get the result you have mentioned in your response. Is this a bug?
-
Sergio Tulentsev over 11 yearsI use 1.9.3. Let me try in 1.8.7
-
18bytes over 11 yearsI understand that the food variable is shadowed inside the block, so its a different variable than the outer one. When i do not use any destructive update (i am not using bang-version), why does it update the outer variable?
-
Sergio Tulentsev over 11 yearsIt should not. Hold on, I'm installing 1.8.7. By the way, why don't you upgrade? Support for 1.8.7 will be dropped in next rails.
-
Sergio Tulentsev over 11 yearsYes, apparently, this is the expected behaviour in ruby 1.8. A blog post.
-
Sergio Tulentsev over 11 yearsThe question was (I guess) why the shadowing persists after
each
is over. -
Michał Szajbe over 11 yearsfor the same reason: you operate in the context the block is defined in, so you can overwrite variables there. I'll update my answer.
-
Sergio Tulentsev over 11 yearsAnyway, ruby 1.9 behaves correctly. It does not overwrite names from outer scope (in case of block parameters).
-
Asad Saeeduddin about 10 years@SergioTulentsev I'm a bit confused about what's going on here. The unexpected part about OP's output in 1.8 was that the final
"Wine"
was capitalised, since thecapitalise
method being invoked is non destructive. Even if thefood
variable holding an array is overwritten by thefood
parameter, why is the final value of the parameter"Wine"
, and not"wine"
? -
Sergio Tulentsev about 10 years@Asad: off the top of my head, I don't know. And frankly, I don't want to know. 1.8 is dead.