Gary Wright
1/20/2006 10:56:00 PM
On Jan 20, 2006, at 2:53 PM, Tom Allison wrote:
> This:
> Variable Scope, pages 105 through 107
> "any new locals created (in while, until, for) will be available
> afterward"
> I've never seen anything like this.
> Anything that is declared (or implicitly declared in ruby) within a
> block
> (conditional or loop) is always out-of-scope when you leave that
> block.
The terminology used in the Pickaxe can be inconsistent or ambiguous at
times. My copy is littered with my own notes written in the margin as
I said "Huh?" and then later figured out what was actually meant.
Here is my go at explaining it. It is long but I hope clear (and
correct!).
The while, until, and for loop constructs don't create a new scope so,
from the point of view of local variables, these constructs don't change
the rules in any way. To the extent that other languages do
introduce a new
variable scope via their looping constructs this may be unexpected
but the
resulting behavior, I think, is readily understood once you realize
that no
new scope is introduced by the while, until or for constructs.
Conditionals
don't introduce new scopes either (if/unless/case), nor does a begin/
end block.
Blocks *do* create a new scope and one that is somewhat different
than people
expect. Since Enumerable#each and Kernel#loop are methods that
accept blocks
it *seems* as those these looping constructs have special rules but
it is
really the blocks that introduce the scoping issue not the methods. Any
method that accepts a block would introduce the same scoping issues,
not just
the blocks associated with 'each' and 'loop'.
So what is special about blocks?
a) local variables that are *created* in the block, *live* and
*die* in the block.
They are not visible outside the block.
b) local variables that are visible when the block is defined are
visible within the block
when it executes (the block captures the lexical environment
in which it is defined)
c) block arguments behave like local variables, *not* like method
arguments
Consider:
a = 4
[5].each { |x| puts a,x }
puts a,x
a is visible when the block is defined (i.e. when each is called) so
it is visible
when the block executes
x is *not* visible when the block is defined but as part of the calling
sequence, when each passes 5 to the block, x = 5 is executed. This
'defines'
the variable x within the block (since it didn't previously exist).
This
is the behavior described in c) above. Since x was created in the
block it
remains visible *only* in the block. When 'puts a,x' runs outside the
block
x is undefined.
Now consider:
a = 4
x = 5
[6].each { |x| puts a,x }
puts a,x
The situation with a is unchanged in this example but is different
for x.
Since x is visible when the block is defined, the assignment that is
implicit
when the block is called, x = 6, simply changes the value of the
existing variable x.
So, in this case, no new variable is created inside the block.
Outside the block,
x is still visible and reflects any changes made while the block
executed (because
the x in the block is the same as the x outside the block).
I think the thing that is most surprising about this behavior is that
block arguments
behave like local variables and don't 'shadow' an identically named
variable in the
enclosing scope.
Hope this helps. And if it doesn't then feel free to point out the
un-helpful parts.
Gary Wright