Loops: while, foreach and ranges
Repeating work over a list or a range, the loop that reads like a sentence, the variable Perl offers you for free, and how to leave a loop early.
Walking a list
foreach hands you the items one at a time: foreach my $step (1 .. 4) { ... }. The parentheses hold anything that is a list — a range, an array, a handful of values — and the body runs once per item with the variable set to it.
A range is a list written with two dots: 1 .. 4 is the numbers one to four, and 'a' .. 'e' is the letters a to e. for and foreach are the same keyword, and foreach is the one to write when you mean a list. The C-style counting loop — for (my $i = 0; $i < 3; $i++) — has three parts inside its parentheses and is what for is usually written for.
While, and an ending you have to write
while repeats for as long as a condition holds: while ($left > 0) { ... $left--; }. Something inside the body has to move that condition towards false, and if nothing does, the loop never ends. A runaway program here simply runs out of time, and the output pane keeps whatever it managed to print.
Choosing between the two is easy. If you have a list, or the number of turns is known, foreach says so. If you are repeating until something becomes true — a total passing a limit, a list becoming empty — while is the loop that fits.
The variable Perl offers, and two words that steer
Name no variable and each item arrives in $_: foreach (@fruit) { print "$_\n"; }. That is Perl doing the typing for you, and it is safe while the loop is short — once a body grows past a couple of lines, name the variable and the reading gets easier.
Two words steer any loop. next skips to the following item and last leaves the loop altogether, and both are usually written as a modifier: next if $n % 2 == 0; reads as the condition it is, with the action in front of it.
The loop variable is an alias
foreach my $item (@list) does not copy each value into $item; it makes $item another name for the element in the list. Writing to $item therefore writes to the array. $_ behaves the same way, which is useful for trimming every value in place and dangerous when you meant to leave the original alone.
If you want to change a copy, copy it first: my $value = $item; and work on $value. That one line is the difference between transforming a list and quietly rewriting the data a later part of the program still needs.
Nested loops need a name
last and next steer the innermost loop around them. When two loops are nested and you need to leave both, put a label in front of the outer one — OUTER: foreach ... — and write last OUTER. The label is not a variable and costs nothing; it is simply a name for the loop.
A label also makes the intent visible. last on its own says "leave this loop"; last OUTER says "stop searching", which is usually the actual thought. Use labels sparingly, but do use one when the alternative is a flag variable tested at every level. A search is the clearest case: the inner loop tests candidates and the outer one supplies them.
A line built inside the loop
A loop that assembles a line starts with an empty string and appends each word plus a space inside the body. The trailing space is the usual bug, and the usual repair is to join instead, or to trim the result once at the end.
Writing the loop once is worth it, because it shows what join does for you: it places the separator between items and never after the last one.
Four loops, and two ways to leave one
use strict;
use warnings;
foreach my $n (1 .. 4) { print "step $n\n"; }
my @fruit = ('apple', 'fig', 'plum');
foreach (@fruit) { print "fruit: $_\n"; }
my $left = 3;
while ($left > 0) {
print "left $left\n";
$left--;
}
for (my $i = 0; $i < 3; $i++) { print "tick $i\n"; }
foreach my $n (1 .. 9) {
next if $n % 2 == 0;
print "odd $n\n";
last if $n > 6;
}Counting up, down and skipping
Run it and check that the last loop stops where you expect. Then change 9 to 20, print the odd numbers to 9 as well, and see how far past 6 the loop now goes.
A loop that builds a list by pushing into it is the long way round once you know map, and counting with a C-style loop is the long way round once you know foreach. Both are still worth writing until the short version reads more clearly to you.
Three loop mistakes cost the most time. A while whose body never moves its condition runs until the sandbox stops it. Pushing into or deleting from a list while a foreach walks it changes the ground under the loop, so build a new list instead. And last inside two nested loops leaves only the inner one; give the outer loop a label when you mean both.
Try these next
- Print a five-by-five multiplication table with two nested loops, and label them so a match can stop both.
- Walk a list of numbers with
foreach, double each element in place, then print the list to prove the alias was real. - Rewrite a counting
whileloop as aforeachover a range, and check the last value is the same.
Find the first pair that adds up
Run it and check which pair was found. Then change the target and see where the search stops. Add a pair that would match only after the current one.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does foreach my $n (1 .. 4) walk over?
