Tweet

Understanding the foreach keyword

A foreach loop is used to iterate over each element of a list.

The basic syntax is: foreach VAR (LIST) BLOCK

Where the variable VAR is set to each value of the LIST in turn and the code in BLOCK is executed. (By BLOCK we mean code enclosed in braces).

Or, to put this another way, consider the following code sample:

    foreach my $var (@list) { ...some code...}

Each element in @list is assigned to $var in turn, and the code in the curly braces is executed.

In Perl, a list can be a simple list of values:

    (0,5,3,2)

or produced by an expression like:

    (0 .. 9)  or  ('a' .. 'z')

or from an array like:

    @orders

or a function that returns a list:

    split(':', $etc_passwd_line)
    or
    keys %hash

or any combination of these.

So you can use foreach to do something with every element of an array:

    foreach my $name (@to_delete)
    {
        CORE::delete $self->{$name};
        $to_delete{$name}++;
    }

or every element of a list:

   foreach (0..$records) {
       my $q = new CGI;
       $q->param(-name=>'counter',-value=>$_);
       $q->save(\*OUT);
   }

or the keys (and values) of a hash:

    foreach (keys %ENV) {
        push(@p,$_) if /^HTTP/;
    }

or combinations of the above:

    foreach my $item ('a' .. 'z',0,5,3,2) {
        print "item: $item\n";
    }

So with that introduction out of the way, let's look at the foreach mechanism in a little more detail, because there are some subtleties here that you will want to be aware of.

Iterating through each element in a list - no control variable

If the variable after the foreach keyword is omitted, the Perl 'topic' variable $_ is set to each value.

    #!/usr/bin/perl
    use strict;
    use warnings;

    my @weekdays = qw(monday tuesday wednesday thursday friday saturday sunday);

    foreach (@weekdays) {
        print $_ . "\n";
    }

This produces the following output:

    monday
    tuesday
    wednesday
    thursday
    friday
    saturday
    sunday

Iterating through a list using a private control variable

If a variable is supplied after the foreach keyword, it is used to receive each value of the list in turn. If it is preceded with the "my" keyword, it is visible only within the loop. This is more formally called 'lexically scoped'.

    #!/usr/bin/perl
    use strict;
    use warnings;

    my @weekdays = qw(monday tuesday wednesday thursday friday saturday sunday);

    foreach my $day (@weekdays) {
        print $day . "\n";
    }

This produces the same output as the example above.

This code fragment shows more clearly that the scope of the $day variable after the foreach keyword is limited to the block of the foreach loop, (the code inside the braces).

    #!/usr/bin/perl
    use strict;
    use warnings;

    my @weekdays = qw(monday tuesday wednesday thursday friday saturday sunday);
    my $day = 'excellent';
    print 'Value of $day before the loop: ' . $day . "\n";

    foreach my $day (@weekdays) {
        print $day . "\n";
    }

    print 'Value of $day after the loop: ' . $day . "\n";

This code produces:

    Value of $day before the loop: excellent
    monday
    tuesday
    wednesday
    thursday
    friday
    saturday
    sunday
    Value of $day after the loop: excellent

Beware of how foreach can localize the control variable...

If you don't qualify the control variable with a 'my', the variable is nonetheless localized within the foreach loop block.

In this example $day is declared outside the foreach loop, but when it is used in the loop, its value is implicitly localized to the loop.

This behavior is unique to foreach loops.

    #!/usr/bin/perl
    use strict;
    use warnings;

    my @weekdays = qw(monday tuesday wednesday thursday friday saturday sunday);

    my $day = 'excellent';
    print 'Value of $day before the loop: ' . $day . "\n";
    foreach $day (@weekdays) {
        print $day . "\n";
    }

    print 'Value of $day after the loop: ' . $day . "\n";

As this output shows, $day regains its previous value after the loop. It does not, (as you might expect), retain the last value from the loop.

    Value of $day before the loop: excellent
    monday
    tuesday
    wednesday
    thursday
    friday
    saturday
    sunday
    Value of $day after the loop: excellent

Side-effects : The control variable is an alias to the list element

If the control variable is on the left-side of an '=' (i.e. in formal language, it is an 'lvalue'), it will modify the corresponding element in the LIST. This is because the control variable is 'aliased' to the element in the list, and changes to the alias propagate to the real element.

    #!/usr/bin/perl
    use strict;
    use warnings;

    my @weekdays = qw(monday tuesday wednesday thursday friday saturday sunday);

    foreach (@weekdays) {
        $_ = ucfirst($_);
    }

    #  and later...

    foreach (@weekdays) {
        print "Week day: $_\n";
    }

Because we assigned the return value of the ucfirst function to the topic variable $_, we also assign that value to the list element itself. In other words, the first element of the @weekdays array was monday, but after the loop, it has been changed to Monday, (and tuesday has become Tuesday, and so on).

    Week day: Monday
    Week day: Tuesday
    Week day: Wednesday
    Week day: Thursday
    Week day: Friday
    Week day: Saturday
    Week day: Sunday

And of course, this behavior is the same if we use an explicit control variable, such as $day, rather than the topic variable $_:

    foreach my $day (@weekdays) {
        $day = ucfirst($day);
    }

But the list element must be capable of being modified for this to work

You can't modify the alias if the thing it is aliasing isn't capable of being modified.

    #!/usr/bin/perl
    use strict;
    use warnings;

    # WRONG - don't do this.

    # If any element of LIST is NOT an lvalue, any attempt to modify 
    # that element will fail.

    foreach my $day (qw(monday tuesday wednesday thursday friday saturday sunday)) {
        $day = ucfirst($day);
    }

This code will result in the warning:

    Modification of a read-only value attempted

c-style for loops

Whilst a c-style for loop is possible, it's rarely necessary.

    #!/usr/bin/perl
    use strict;
    use warnings;

    my @sorted_appointments = qw(9AM 10AM 2PM 2PM 4PM);

    for (my $index = 0; $index <= $#sorted_appointments; $index++) {
        if ($sorted_appointments[$index - 1] eq $sorted_appointments[$index]) {
            print "You have two appointments at the same time "
                  . "(at $sorted_appointments[$index])\n"
        }
    }

The Perl range operator .. is a much more compact way of doing this:

    #!/usr/bin/perl
    use strict;
    use warnings;

    my @sorted_appointments = qw(9AM 10AM 2PM 2PM 4PM);

    foreach my $index (1 .. $#sorted_appointments) {
        if ($sorted_appointments[$index - 1] eq $sorted_appointments[$index]) {
            print "You have two appointments at the same time "
                  . "(at $sorted_appointments[$index])\n"
        }
    }

    You have two appointments at the same time (at 2PM)

A word on statement modifiers

Perl supports the idea of modifiers that can be appended to a simple statement.

You may have seen simple statements modified in this way, such as:

    print "Some message\n" if ($some_condition_is_met);

In this case, print "Some message\n" is the simple statement and if ($some_condition_is_met) is the modifier.

The foreach modifier executes the statement that precedes it once for each item in the list.

    print length($_) . "\n" foreach qw(There's more than one way to do it);

which outputs:

    7
    4
    4
    3
    3
    2
    2
    2

In the case above, print length($_) . "\n" is the simple statement and foreach qw(There's more than one way to do it) is the modifier.

More Advanced Usage - Labels

The full syntax for a foreach loop is more formally:

LABEL: foreach VAR (LIST) BLOCK

Where LABEL is a literal value , like OUTER or TEACHERS for example. Labels are traditionally typed in upper case to highlight their presence. They act like the labels you see in a goto or a here document.

So, how do we use a label in a foreach block? Like this:

    TEACHER: foreach my $teacher (@teachers) {
        STUDENT: foreach my $student ($database->getStudents($teacher)) {
            next TEACHER if $student->canTeach();    # student can do
                                                     # it for the teacher
            next STUDENT if $student->unteachable(); # don't waste
                                                     # teachers time...
            # do stuff with $teacher and $student 
        }
    }

Basically, we can use labels to short circuit to the next element in the current loop, or an outer loop. Well chosen label names can also be self-documenting to some extent.

See also

    perldoc perlsyn
      Look for the sections on:
        Statement Modifiers
        Compound Statements
            For Loops
            Foreach Loops
    perldoc -f map
    perldoc -f grep
    perldoc -q "difference between a list and an array"
    Discussions about statement modifiers in 
    Perl Best Practices

[Top]