I\'m kind of new to Ruby and some of the closure logic has me a confused. Consider this code:
array = []
for i in (1..5)
array << lambda {i}
end
arra
What version of Ruby are you running this on? 1.8 does not have block scope for local variables, so j is still hanging around (and equal to 5) even after the end of the for
.
Okay, this is getting ridiculous. Every time I try to answer a question about how for
loops work in Ruby, I get it wrong.
The reason for this is, of course, that I don't use for
loops in Ruby, neither does anybody else, so it really doesn't matter to me :-)
Anyway, to settle the matter once and for all, I went straight to the ultimate source, the December 1, 2009 preliminary Draft of the IPA Ruby Language Specification (destined to become the ISO Ruby Language Specification):
§11.4.1.2.3 The
for
expressionSyntax
- for-expression → for for-variable in expression do-clause end
- for-variable → left-hand-side | multiple-left-hand-side
The expression of a for-expression shall not be a jump-expression.
Semantics
A for-expression is evaluated as follows:
- Evaluate the expression. Let
O
be the resulting value.Let
E
be the primary-method-invocation of the form primary-expression [no line-terminator here].each do | block-formal-argument-list | block-body end, where the value of the primary-expression isO
,the block-formal-argument-list is the for-variable, the block-body is the compound-statement of the do-clause.Evaluate
E
, but skip Step c of §11.2.2.The value of the for-expression is the resulting value of the invocation.
Okay, so basically this means that
for for_variable in expression
do_clause
end
gets translated to
O = expression
O.each do |for_variable|
do_clause
end
Or, in your case:
for i in 1..5
puts j if i > 1 #undefined local variable or method `j' (NameError)
j = i
end
gets translated to
(1..5).each do |i|
puts j if i > 1 #no excpetion here, works just fine ??!!??
j = i
end
Aha! But we forgot something! There's this ominous "skip Step c of §11.2.2." thing! So, what does it say?
- Push an empty set of local variable bindings onto ⟦local-variable-bindings⟧.
Note that Step b
- Set the execution context to
E
b
.
is not skipped.
So, as far as I can see, a for
loop gets its own execution context, which starts out as a copy of the current execution context, but it does not get its own set of local variable bindings. IOW: it gets its own dynamic execution context, but not its own lexical scope.
I must admit, I'm still not sure I fully understand it, but it doesn't get any more precise than this.