For
Video transcript & code
When you're new to Ruby, having come from some other language like Java or Perl, the heavy reliance on methods taking blocks can seem alien and confusing at first. As a result, you might be tempted to rely on more familiar constructs. One such familiar feature you may have used is Ruby's for
loop.
At first glance, the for
loop is similar to foreach
loops found in other languages. We write for
, followed by the name of a loop variable, followed by the word in
, and then a list of things we want to iterate over. Inside the loop, we perform some operation. Then we end the loop with an end
.
for grocery in %W[milk bread cheese]
puts "Don't forget to buy #{grocery}"
end
# >> Don't forget to buy milk
# >> Don't forget to buy bread
# >> Don't forget to buy cheese
We can also loop over a range of numbers if we want.
for n in 1..3
puts n
end
# >> 1
# >> 2
# >> 3
for
loops look so basic and so similar to the loops found in other languages that we might be tempted to think of them as "simpler" or more "low level" constructs than sending .each
to an object. But this isn't really the case at all. In fact, a for
loop is simply syntax sugar for calling .each
. The loop we started with is equivalent to an idiomatic Ruby .each
loop:
%W[milk bread cheese].each do |grocery|
puts "Don't forget to buy #{grocery}"
end
# >> Don't forget to buy milk
# >> Don't forget to buy bread
# >> Don't forget to buy cheese
What do I mean by "equivalent"? To find out just how equivalent these two forms are, lets write a new class with just one method: #each
. Then let's instantiate an object.
First, we'll try explicitly sending it the #each
message.
Then we'll change it to a for loop.
class GroceryList
def each
yield "yams"
yield "leeks"
yield "rutabagas"
end
end
list = GroceryList.new
for grocery in list
puts "Don't forget to buy #{grocery}"
end
# >> Don't forget to buy yams
# >> Don't forget to buy leeks
# >> Don't forget to buy rutabagas
As we can see, the for
loop is simply using the #each
method we defined.
And that's exactly what was going on before as well. When we used a for
loop on an array, Ruby used that array's #each
method. Ruby Range
objects also have an #each
method, which is what makes it possible to use them in a for
loop.
(1..3).each do |n|
puts n
end
# >> 1
# >> 2
# >> 3
Apart from the difference in syntax, there is exactly one way in which a for
loop differs from .each
. Unlike a block, the for
loop does not introduce a new block scope for variables.
Let me demonstrate what I mean. If we try to take a look at the value of n
after the loop, we get an error. The block argument n
was only valid within the body of the loop.
(1..3).each do |n|
puts n
end
puts "Ending value of n: #{n}"
# ~> -:4:in `<main>': undefined local variable or method `n' for main:Object (NameError)
# >> 1
# >> 2
# >> 3
But if we change this code to a for
loop, we can see that at the end of execution, the final value of n
hangs around.
for n in (1..3)
puts n
end
puts "Ending value of n: #{n}"
# >> 1
# >> 2
# >> 3
# >> Ending value of n: 3
This could conceivably get us into trouble someday. For instance, here's a contrived method which counts down from a number n
, using a for
loop. At the end, it's supposed to reiterate what number it counted down from. But when we execute it with the number 4, it claims that it counted down from 1.
def countdown_from(n)
for n in n.downto(1)
puts n
end
puts "I counted down from #{n}"
end
countdown_from(4)
# >> 4
# >> 3
# >> 2
# >> 1
# >> I counted down from 1
This is because we foolishly re-used the variable name n
from the method's parameter list as the loop counter as well. Since the for
loop executes in the same variable scope as the surrounding code, the original argument is overwritten.
Compare this to what happens when we rewrite the code to use an #each
loop.
def countdown_from(n)
n.downto(1).each do |n|
puts n
end
puts "I counted down from #{n}"
end
countdown_from(4)
# >> 4
# >> 3
# >> 2
# >> 1
# >> I counted down from 4
This time, we get the output we expected. This happens because the .each
block introduces a new variable scope. The n
variable inside this scope "shadows" the outer n
, and when the block ends, the inner n
variable goes away as well.
So now that we understand what a for
loop is and how it differs from #each
, what exactly is for
good for?
I've heard it claimed that a for
loop somehow "looks" better in mixed Ruby/HTML templating languages such as ERB. But I don't find this argument very persuasive.
And I'll be honest: in over a decade of writing Ruby code, I have not once found a reason to prefer a for
loop's version of variable scoping. It offers no advantages, and slightly increases the chances of coding errors.
Add to that the fact that Ruby essentially compiles it into a call to #each
, but the for
syntax obscures what's really going on.
The upshot of all this is that in my opinion, the best thing to with Ruby's for
loops is nothing at all. Pretend they don't exist. If you use them anywhere in your code, get out of the habit. If you don't use them, great; there's no reason to start.
.each
loops with blocks might be confusing when we're first getting into the language, but the concept of telling collections to iterate over themselves using .each
messages is fundamental to the Ruby way. The sooner we internalize that model, the better for us as Ruby programmers.
Happy hacking!
Responses