What Is The Purpose of Setting Ruby Block Local Variables When Blocks Have Their Own Scope Already

What is the purpose of setting Ruby block local variables when blocks have their own scope already?

Block scopes nest inside their lexically enclosing scope:

foo = :outerfoo
bar = :outerbar

1.times do |;bar|
foo = :innerfoo
bar = :innerbar
baz = :innerbaz
end

foo #=> :innerfoo
bar #=> :outerbar
baz # NameError

You need a way to tell Ruby: "I don't want this variable from the outer scope, I want a fresh one." That's what block local variables do.

ruby: can a block affect local variables in a method?

First of all, block.call() is done with yield, and you don't need the &block parameter that way.

You can't normally do what you want, blocks are bound when they are created, and inside the block you can see the local variables defined at that moment; the easiest way to do what you want, which is not how you will use blocks normally, is this:

def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end

test() {
foo="this is foo"
}

But that's only a side effect because foo is "returned" by the block. If you instead do this:

def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end

test() {
foo="this is foo"
"ha ha, no foo for you"
}

You'll notice that it does something different.

Here's more magic:

def test(&block)
foo = eval "foo", block.binding
puts foo
block.call
foo = eval "foo", block.binding
puts foo
end

foo = "before test"
test() {
foo = "after test"
"ha ha, no foo for you"
}

That would kind of work, but it breaks if you remove foo = "before test" because foo becomes a local variable in the block and does not exist in the binding.

Summary: you can't access local variables from a block, just the locals where the block was defined and the return value of the block.

Even this won't work:

def test(&block)
eval "foo = 'go fish'", block.binding
block.call
bar = eval "foo", block.binding
puts bar
end

because the foo in the binding is different from the local in the block (I didn't know this, thanks).

Ruby, understanding local variables scope in if block

In ruby, classes, modules, functions, blocks and procs all have their own scope, so local variables defined within them will not normally be accessible outside of them.

In ruby, logic statements such as if, while and for do not have their own scope, so variables defined in them persist in the class, module, function block, or proc where they're used.

This is a design choice and part of what makes ruby ruby! It may feel counter-intuitive because languages like c have separate scopes for if statements and some (but not all) interpreted languages mimic that.

Local variable acessible outside block

In Ruby, a "block" is basically code that is enclosed either inside a do...end or between curly braces. (There are other ways — sort of — to create blocks in Ruby, but this definition will hold you for quite a while.) if...end isn't considered a block. Neither is while...end. To further complicate the issue, the do that you add after the while doesn't make it a block, either; Ruby just helpfully ignores it.

Cary has already explained this in the comments, but I'll see if I can clarify it a bit for you. I'll simplify and clean up your code a bit:

a = 3
while a < 10
b = a * 3
a += 1
end
puts b

This will give 27. But now, if I do this:

a = 3
loop do
b = a * 3
a += 1
break if a == 10
end
puts b

I'll get this:

undefined local variable or method `b' for main:Object (NameError)

which is what you were expecting. This is because while is a statement. while...end may enclose several lines of code, but it doesn't count as a block. (Ditto for if.) But loop is actually a method that takes a block as an argument, so the scoping rules that Cary describes in his comment apply.

What is the specific difference between block variable and block-local variable in Ruby?

Unfortunately there are no suitable keywords in ruby which might explain it beyond the doubt. So, let me translate it to javascript! (Javascript local variables are practically identical to ruby variables - with an exception for explicit creation keywords)

But before, few notes about scopes in javascript. Even though I'll be writing using JS, all the notes are also correct for ruby - except for a lack of the explicit variable keyword.

So: let keyword - let creates a new variable on the current scope. Once created, given variable can be read only when we are inside the same lexical scope or its child scopes:

let x = 0;

x #=> 0;

function() { # function creates a new child scope
return x; # access variable of the parent scope
}() #=> 0

It is important to understand that the scope is lexical, not dynamic - this means that variables resolves in a static context, which depends on the structure of the code rather than how the code is being called. This creates so-called closures (again - closures also exists in Ruby, however, unlike in JS, it's best to avoid them).

When searching for a variable, we always look at the current scope first and, if we have nothing defined in current scope, move to the parent scope. If no parent scope is found, exception is thrown.

So, let's translate your code into javascript:

let x = 0, y = 0, z = 0        
let ary = [1, 2, 3]

ary.forEach(function(x) { # x is now function argument
let y; # This is because of `|...; y|` in your block

# In this scope we have 3 variables:
# x - is an argument of a function
# y - is locally scoped variable
# z - is undefined in this scope, so it'll reference z from the parent scope
y = x
z = x
console.log(x, y, z)
})

console.log(x, y, z)

Let's analyze. In the above code, there are two scopes - top scope and child scope. There are 4 variables defined in your top scope (x, y, z and ary) and 2 variables defined in the child scope (x, y).

When you do y = x you first read the value of x - since such a variable exists in the current scope (and it is passed as an argument), we take that. In first iteration the value of x is 1, so expression evaluates to:

y = 1

Now we need to find variable y we want to assign to - there is variable y in the current scope so we assign to that one. The y variable in the main scope is not affected by this assignment

Next expression: z = x - x resolves the same way as before, so in first iteration we have:

z = 1

But now, there is no z variable in the current scope, so we look for a variable in the parent scope. In result, this assignment modifies the variable in the main scope

Next, we're printing x, y and z resolved in the current scope - there's no surprise here, they are all 1s.

Second iteration, everything repeats, but this time x resolves to 2. Again, we set the value of a y in a local, child scope and z of the parent scope. And then, third iteration.

In last expression we print the values of the variables, but in the parent scope. x is 0 as we have never done a single assignment to it, similarly y is 0 - because all the assignments were done against the variable defined in the child scope so, in fact, we didn't make a single assignment to it. z on the other hand was assigned to on each iteration. On the last iteration it received value 3 which is what it currently holds.

Block scope in ruby

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"]

Scopes and blocks

In ruby, module, class, and def keywords define a new scope.

There are six scope constructs in Ruby: module bodies, class bodies, method bodies and script bodies create new scopes, block bodies and "stabby lambda" literal bodies create new nested scopes.

I'm confused as to why local variables defined in a block do not exist outside of the block.

Because a block body has its own lexical scope. The scope is nested, which means that it has access to local variables from the outer scope, but variables from an inner scope never leak into the outer scope.



Related Topics



Leave a reply



Submit