It's been a very long time that I've made a post that wasn't related to The Weekly Challenge, but it's about time I did :)
Today's post is about the unexpected output of a script when using the each function in Perl inside a loop.
Example script
Let's take a look at an oversimplified example:
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
my %favorite = (
Simon => 'blue',
Tom => 'brown',
Dick => 'brown',
Harry => 'red',
);
OUTER: for (1 .. 5) {
while (my($name, $color) = each %favorite) {
next OUTER if $color eq 'brown';
}
say "Oh dear!";
exit;
}
This seems straight forward enough. We have an outer loop that iterates five times. If any person has a favorite color of 'brown' we exit the inner loop, and 'Oh dear!' is never printed. So lets run that.
$ ./each.pl
Oh dear!
What went wrong
Even to an experienced Perl developer, this is probably not the expected result. So what happened?
Lets add some debugging output to the script
OUTER: for (1 .. 5) {
say "count: $_";
while (my($name, $color) = each %favorite) {
say "name: $name, color: $color";
next OUTER if $color eq 'brown';
}
say "Oh dear!";
exit;
}
The output is
$ ./each.pl
count: 1
name: Harry, color: red
name: Tom, color: brown
count: 2
name: Dick, color: brown
count: 3
name: Simon, color: blue
Oh dear!
What this shows is that even though we restart the outer loop, the inner each
loop does not reset for each iteration.
This is documented in the each man page.
The iterator used by each is attached to the hash or array, and is shared between all iteration operations applied to the same hash or array. Thus all uses of each on a single hash or array advance the same iterator location.
Solution
The easiest solution is to always use the keys function when iterating over a hash inside a loop. Therefore a possible solution would be to write something like the below.
OUTER: for (1 .. 5) {
foreach my $name (keys %favorite) {
next OUTER if $favorite{$name} eq 'brown';
}
say "Oh dear!";
exit;
}
This produces no output, as one would expect.
$ ./each.pl
$
Top comments (1)
A few more solutions spring to mind,