while loops that have an index

Perl got this syntax that allow to use a while loop without having to explicitly increment an index by doing an i++. It is made possible by the each function.

Let's demonstrate this in a simple test that check that and array and an array ref contains the same things:

# t/01_foo_order.t    
use v5.18;
use Test::More tests => 3; 

my $events_arr_ref = get_events();
my @expected_events = ('foo', 'bar', 'baz');

while ( my ( $i, $event ) = each( @$events_arr_ref )) {
  is @$events_arr_ref[$i], 
    $expected_events[$i], 
    "Array element [ $i is $expected_events[$i]";
}

done_testing();

sub get_events {
  return [ 'foo', 'bar', 'baz' ];
}

Let's execute our test:

$ prove -v t/01_foo_order.t
1..3
ok 1 - Array element [ 0 ] value is foo
ok 2 - Array element [ 1 ] value is bar
ok 3 - Array element [ 2 ] value is baz
ok
All tests successful.
Files=1, Tests=3,  0 wallclock secs ( 0.03 usr  0.00 sys +  0.07 cusr  0.00 csys =  0.10 CPU)
Result: PASS

while ( my ( $i, $event ) = each( @$events_arr_ref )) {} makes possible to iterate on the $events_arr_ref array reference and for each element found, initializing $i and $event with the right value.

This is quite the same than a for loop except that you don't have to increment the index and that it must be used in case you want to iterate on the whole array.

I use it quite often, can be handsome if you want to avoid $_. Just yet another TIMTOWTDI...

Sources:

6 Comments

I honestly don't find the each @array syntax especially nice, plus I always forget what version of Perl it was introduced in. (In fact, I tend to avoid each %hash too.) For looping through an array with an index, I find this to be pretty readable:

for my $i ( 0 .. $#myarray ) {
  $_ = $myarray[$i];
  ...;
}

PS: this also gives you a neat way of skipping undefined elements:

for my $i ( 0 .. $#myarray ) {
  $_ = $myarray[$i] // next;
  ...;
}

Or skipping false elements:

for my $i ( 0 .. $#myarray ) {
  $_ = $myarray[$i] or next;
  ...;
}

Of course, you can use a lexical instead of $_.

I'm certainly not saying each is a bad way to do things; I've just never really liked it myself. That's mostly because I don't like its behaviour with hashes though, not arrays.

If you make changes to the hash during the loop, it can act weird. Also, the order it loops through the hash is unpredictable.

If I'm going to loop through a hash, I generally use:

for my $key ( sort keys %hash ) {
  my $val = $hash{$key};
  ...;
}

Though for big hashes with a lot of keys, each will perform better, so is worth consideration.

Leave a comment

About Sébastien Feugère

user-pic I am a Perl culture enthusiast since 2011. Currently working at a french opinion institute, I try to build tools using the Perl toolkit, but also Debian, LXC and a bit of JavaScript. Other hobbies: Blogger of dreams about art school (FR) Professional can opener of a teenage cat Psychotherapist for sad computers, would wrap them in blankets and make them tea Translator of a book about net.art (FR) Go to my Gitlab account to discover more.