4

I have a minimal working example of two nested while loops on the same array @g:

#!/usr/bin/env perl

use 5.042;
no source::encoding;
use warnings FATAL => 'all';
use feature 'say';
use autodie ':default';

my @g = qw(Matthew Mark Luke John);
while (my ($i, $gos) = each @g) {
    say "$i => $gos";
    while (my ($j, $g2) = each @g) {
        say "\t$j=>$g2";
    }
}

This particular loop gets stuck on the first iteration $i, and never moves on to the 2nd.

The infinity is easily fixed by foreach my $gos (@gos) { instead of the first while.

Is this a bug or an intended feature?

Shouldn't foreach my $gos (@g) in the first line be the exact equivalent of while (my ($i, $gos) = each @g) {?

2
  • Not just an infinite loop. The inner loop skips index 0 Commented Oct 15 at 13:58
  • 2
    You should state what output you thought you would get. Commented Oct 15 at 15:12

2 Answers 2

5

The problem is that both invocations of each use the same list iterator. From the perldoc:

Each hash or array has its own internal iterator, accessed by each, keys, and values. The iterator is implicitly reset when each has reached the end [...]

After returning all elements, the iterator returns an empty list in the inner loop, upon which it restarts from the beginning. This is why your outer loop never progresses beyond the first element: all remaining elements are consumed by the inner loop on every iteration.

The repeated output you're seeing clearly demonstrates the shared iterator behavior:

0=>Matthew     # Consumed by outer loop
    1=>Mark    # Consumed by inner loop
    2=>Luke    # Consumed by inner loop
    3=>John    # Consumed by inner loop
    ()         # Iterator is exhausted and reset. Loop exits
0=>Matthew     # A fresh iterator
    1=>Mark
    2=>Luke
    3=>John
    ()         # Iterator is exhausted and reset. Loop exits
# ... and so on

You should use foreach instead.

Sign up to request clarification or add additional context in comments.

3 Comments

Just wanna add a bit to what you said: "while-each" loop are risky because exiting one using last, return or an exception can leave the array or hash's iterator in an unexpected place. Using a sufficiently new Perl, you can use for my ($idx, $val) (builtin::indexed @array) as an efficient and safer alternative
indexed is the best solution, IMHO
Especially since you're already enforcing 5.42
4

Since you are using v5.42, you should use foreach and indexed which is designed to do just this:

use 5.042;
$|=1;

my @g = qw(Matthew Mark Luke John);

foreach my($i, $gos) ( indexed @g ) {
    say "$i => $gos";

    foreach my($j, $g2) ( indexed @g ) {
        say "\t$j=>$g2";
        }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.