In the book Eloquent Ruby (page 21, first edition, sixth printing), the author (Russ Olsen) advocates using the each
method instead of the for
loop and this is in line with everything I've read elsewhere.
However the author also goes on to say that one reason to do so is that the for
loop does in fact call the each
method, so why not just cut out the middle-man and use each
? So I was wondering how this actually works.
To investigate I did do a search on the Ruby repo on github, but was finding it difficult to pinpoint where/how I could see this in action.
To restate the question:
How can I show the Ruby for
loop is in fact implemented using the each
method?
You can show it by writing a class that implements each:
# Demo that for calls each
class ThreeOf
def initialize(value)
@value = value
end
def each(&block)
puts "In Each"
block.call(@value)
block.call(@value)
block.call(@value)
end
end
And then make an instance and use it in a for loop:
collection = ThreeOf.new(99)
for i in collection
puts i
end
Run that and you will see the message printed out and the for "loop" will go round three times.
Alternatively (and more fun) you can monkey patch the built in Array class:
class Array
alias_method :orig_each, :each
def each(*args, &block)
puts "Array Each called!"
orig_each(*args, &block)
end
end
puts "For loop with array"
for i in [1,2,3]
puts i
end
And again you will see the message printed.
The semantics of the for
expression are defined in the ISO Ruby Language Specification like this:
§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
is evaluated the same as
O = expression
O.each do |for_variable|
do_clause
end
Aha! But we forgot something! There's this ominous "skip Step c of §11.2.2." thing! So, what does Step c of §11.2.2. 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, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With