Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Block scope in ruby

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 variable food.

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?

like image 986
18bytes Avatar asked Jul 27 '12 10:07

18bytes


2 Answers

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"]
like image 115
Sergio Tulentsev Avatar answered Oct 06 '22 12:10

Sergio Tulentsev


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.

like image 36
Michał Szajbe Avatar answered Oct 06 '22 10:10

Michał Szajbe