Perl Part I · Module 9 · Loops
foreach and the C-Style for
Perl has two loop shapes that both start with the word for, and they are not variations on each other — they answer different questions. foreach walks a list and hands you each item, which is what you want in the large majority of loops you will write. The three-part C-style for counts, and you reach for it when the position matters as much as the value, or when the step is not one. The practical skill is knowing which question you are asking, because the counting form brings an arithmetic bound along with it, and the most common way to get that bound wrong produces one extra iteration that reads a value that was never there and says nothing about it.
Foundation15 min readPerlPart IControl FlowLoops
Part I · Module 9 · Chapter 9.1 · foreach and the C-Style for
1. Three Ways to Walk an Array
use strict;
use warnings;
my @blocks = ("alu", "fifo", "regf");
print "foreach :";
for my $b (@blocks) { print " $b" }
print "\n";
print "for 0..\$# :";
for my $i (0 .. $#blocks) { print " $i=$blocks[$i]" }
print "\n";
print "C-style for :";
for (my $i = 0; $i < @blocks; $i++) { print " $i=$blocks[$i]" }
print "\n";foreach : alu fifo regf
for 0..$# : 0=alu 1=fifo 2=regf
C-style for : 0=alu 1=fifo 2=regfAll three reach the same three elements. The difference is what each one gives you and what it costs to read.
| Form | Gives you | Reach for it when |
|---|---|---|
for my $b (@blocks) | the item | almost always |
for my $i (0 .. $#blocks) | the index | you need the position, or two arrays in step |
for (my $i=0; $i<@blocks; $i++) | the index, with explicit control | the step is not 1, or the condition is not a simple bound |
foreach is the default and for is the exception. That ordering matters: a loop written with an index when it did not need one has an extra variable, an arithmetic bound, and a subscript on every line, all of which can be wrong. A foreach loop has none of those.
for and foreach are the same keyword in Perl — entirely interchangeable in every form. The convention most code follows is foreach for the list form and for for the counting form, purely as a signal to the reader.
Note $i < @blocks in the third line: @blocks in that numeric comparison is its element count, which is Chapter 5.7's rule. That is why the C-style form needs no scalar().
2. The Bound That Is Wrong by One
use strict;
use warnings;
my @blocks = ("alu", "fifo", "regf");
print "0 .. \$#blocks : ";
for my $i (0 .. $#blocks) {
print defined $blocks[$i] ? "$blocks[$i] " : "UNDEF ";
}
print "\n";
print "0 .. scalar(\@blocks): ";
for my $i (0 .. scalar(@blocks)) {
print defined $blocks[$i] ? "$blocks[$i] " : "UNDEF ";
}
print "\n";0 .. $#blocks : alu fifo regf
0 .. scalar(@blocks): alu fifo regf UNDEFOne extra iteration, reading an element that does not exist, and nothing on STDERR.
$#blocks is the last index — 2 here. scalar(@blocks) is the count — 3. A three-element array has indices 0, 1 and 2, so 0 .. 3 visits a fourth position. Chapter 5.2 established that reading past the end of an array is silent and does not extend it, which is exactly why this costs you nothing visible until the body does something with the value.
The only reason this printed UNDEF rather than a blank is that the body tested defined first. Without that test, print "$blocks[$i] " would have produced a warning — but one warning in a run that has others, for a loop that completed normally and exited 0.
| Want | Correct bound |
|---|---|
| every index | 0 .. $#array |
| every index, counting form | my $i = 0; $i < @array; $i++ |
| the count itself | scalar(@array) |
$#array and scalar(@array) differ by exactly one, always. The habit is to use $#array in a range and < @array in a C-style condition — both already correct, so there is no arithmetic to get wrong.
3. When the Counting Form Is Right
Three cases where an index genuinely earns its place.
use strict;
use warnings;
my @b = ("a", "b", "c", "d");
print "reverse :";
print " $_" for reverse @b;
print "\n";
print "every 2nd :";
for (my $i = 0; $i < @b; $i += 2) { print " $b[$i]" }
print "\n";
print "by index, descending:";
for my $i (reverse 0 .. $#b) { print " $i=$b[$i]" }
print "\n";reverse : d c b a
every 2nd : a c
by index, descending: 3=d 2=c 1=b 0=a- Backwards over the values needs no index at all —
reverse @bis a list, andforeachwalks it. The counting form is unnecessary here. - A step other than one does need the C-style form:
$i += 2. - Backwards with the index is
reverse 0 .. $#b, which is clearer than a decrementing C-style loop and cannot get its bound wrong.
Two more legitimate cases:
for my $i (0 .. $#names) {
printf "%-6s %+.2f\n", $names[$i], $slacks[$i];
}
for my $i (0 .. $#values) {
$values[$i] = sprintf("%.2f", $values[$i]);
}Walking two parallel arrays needs the shared index. Writing back into an array by index works too — though Chapter 9.2 shows foreach can modify the array directly, which is usually shorter.
4. The Loop Variable Is Scoped to the Loop
use strict;
use warnings;
our $x = "before";
for $x ("a", "b") { }
print "after the loop, \$x = $x\n";after the loop, $x = before$x came back. Even though the loop used a variable declared outside it and did not say my, foreach gave each iteration its own binding and restored the original afterwards.
That behaviour is called implicit localisation, and it has three consequences worth carrying forward:
- Always write
for my $b (...)anyway. Relying on the implicit version means the reader has to know this rule;mysays it outright, and it is whatuse strictwants in the ordinary case. - The loop variable is not usable after the loop. With
for my $b (...),$bdoes not exist outside. If you need the last value, assign it to a variable declared before the loop. - It is why the closure trap from Chapter 7.11 does not apply to
foreach. Each iteration gets a fresh binding, so closures created inside aforeachcapture distinct values — measured there as1 2 3, where a C-styleforgave3 3 3.
The same mechanism is what makes $_ safe in nested foreach loops, which is Chapter 9.7's subject.
5. Looping Over Things That Are Not Arrays
foreach takes a list, and anything that produces a list works.
use strict;
use warnings;
my %report = (alu => -0.12, fifo => 0.34, regf => -0.05);
my $rows = [ ["alu", -0.12], ["fifo", 0.34] ];
print "a literal list :";
print " $_" for ("a", "b");
print "\n";
print "a range :";
print " $_" for 1 .. 4;
print "\n";
print "sorted keys :";
print " $_" for sort keys %report;
print "\n";
print "a sorted slice :";
print " $_" for (sort { $report{$a} <=> $report{$b} } keys %report)[0 .. 1];
print "\n";
print "through a ref :";
print " $_->[0]" for @$rows;
print "\n";a literal list : a b
a range : 1 2 3 4
sorted keys : alu fifo regf
a sorted slice : alu regf
through a ref : alu fifoNote sort keys %report rather than bare keys — Chapter 6.5 measured that hash order varies between runs, so any loop whose output is read or diffed needs the sort. And @$rows dereferences an array reference, which is Chapter 7.3's notation.
A range is not materialised as a list in a foreach with simple numeric endpoints — Perl optimises for (1 .. 1_000_000) into a counting loop, so it costs no memory. A range built from variables or used anywhere else does build the list.
6. Why This Matters in Engineering Work
Most loops want foreach, and writing it that way removes a class of bug rather than avoiding one. No index means no bound, no subscript and no arithmetic — so there is nothing to be off by one about.
The bound mistake fires on the inputs you test least. 0 .. scalar(@a) costs one silent undef on a populated array and one whole wrong iteration on an empty one. A report with zero failing blocks is normal, routine, and exactly the case that exercises it.
And an index-based loop that edits its own array is a real shape. Filtering violations in place by splice inside a counting loop visited three of five in the measurement above and reported success. Rebuilding with grep has no index to desynchronise.
7. Common Mistakes
0 .. scalar(@a). One iteration too many. Use 0 .. $#a.
0 .. @a - 1 where the array might be empty. That is 0 .. -1, which is correct — but written as 0 .. $#a it says so directly.
Using an index when you only need the item. Extra variable, extra bound, extra subscript.
splice or delete inside a counting loop over the same array. Elements get skipped. Iterate backwards, or rebuild with grep.
Appending to the array a C-style loop is iterating. The condition is re-checked each pass, so the loop grows.
Expecting the loop variable to survive the loop. foreach localises it, and my scopes it to the body.
for my $k (keys %h) where the output is read. Add sort; hash order varies per run.
8. Exercise
Exercise 1 — three forms. Walk a three-element array with foreach, with 0 .. $#a, and with a C-style for. Confirm all three reach the same elements and note which gave you the index.
Exercise 2 — the off-by-one. Run both bounds over the same array with a defined test in the body. Record the two output lines, then remove the defined test and record what reaches STDERR and what the exit status is.
Exercise 3 — the empty array. Repeat Exercise 2 on an empty array. Report the iteration count for foreach, for 0 .. $#a, and for 0 .. scalar(@a). Print $#a and explain the third result.
Exercise 4 — when the index earns it. Write three loops: backwards over the values, every second element, and backwards with the index. Say which of the three did not need the counting form.
Exercise 5 — two arrays in step. Build @names and @slacks of the same length and print them side by side with one printf per row. Then make @slacks one element shorter and record what happens.
Exercise 6 — the shrinking array. Reproduce §3's splice loop. Record which elements were visited and which were left. Then fix it twice — once by iterating backwards, once with grep — and confirm both visit all five.
Exercise 7 — implicit localisation. Declare our $x = "before", loop with for $x (...), and print $x afterwards. Then do the same with for my $x (...) and try to print $x after the loop; record the error.
Exercise 8 — lists that are not arrays. Loop over a literal list, a range, sort keys %h, and an array reference with @$ref. Then loop over bare keys %h twice and say whether the order was the same.
9. Summary
foreachwalks a list and hands you the item; the C-styleforcounts.forandforeachare the same keyword.foreachis the default. No index means no bound, no subscript and no arithmetic to get wrong.$#arrayis the last index;scalar(@array)is the count. They differ by exactly one, always.0 .. scalar(@a)runs one iteration too far, reading a silentundef— measured, with empty STDERR.- On an empty array
$#ais-1, so0 .. $#aruns zero times while0 .. scalar(@a)runs once. - In a C-style condition use
$i < @array— the array in numeric context is its count, so noscalar()is needed. - The counting form earns its place for a step other than 1, for two arrays in step, and for modifying by index.
spliceinside a counting loop over the same array skips elements — measured 3 of 5 visited, exit 0. Iterate backwards or rebuild withgrep.foreachimplicitly localises its loop variable, restoring an outer variable afterwards — which is why closures created inside aforeachcapture distinct values.- Sort
keyswhenever the loop's output is read or diffed.
Chapter 9.2 takes up the consequence of that loop variable being a name for the array element rather than a copy of it: assigning to it changes the array.
Where this fits
Part of the Perl curriculum.
