http://qs1969.pair.com?node_id=495783

inq123 has asked for the wisdom of the Perl Monks concerning the following question:

Hi, yesterday I got a strange bug that took a while to fix. It also took a while to trace down the bug enough to recreate it in the short script below (original script was too long to post).

Basically the symptom is that first I create a hash of hash of hash, then use a while(my ($key, $value) = each %hash) to loop through it, then inside the while loops, use 'last;' to break them. After that, create a second set of those while loops, and the problem is, the 2nd set of the while loops won't be entered. If I don't use 'last;', or if I use 'for my $key (keys %hash)', or if I use a dump immediately before the 2nd set of the while loops, the problem would be "fixed".

But I want to understand why the 2nd set of while loops do not work in the first place. Anyone could help? BTW I tested it on Perl on UNIX and Windows, 5.6.1 - 5.8.5.

Without further ado, here's the script:

use strict; use Dumpvalue; my %test = ('level1' => { 'level2' => { 'level3' => { 'level4' => 1} } + } ); #Dumpvalue->new->dumpValue(\%test); # when uncommented, this dump ## doesn't help the troubled while loops my $createtrouble = 1; if($createtrouble) { ################################################### # the woes of the troubled while loops further below is # really caused by the 'last's in these while loops while(my ($key1, $sdata) = each %test) { while(my ($key2, $stats) = each %$sdata) { last; } last; } } #Dumpvalue->new->dumpValue(\%test); # when uncommented, this dump ## mysteriously helps the troubled while loops print "Start print out now\n\n"; my $usewhile = 1; if($usewhile) { ################################################# # the troubled while loops while(my ($key1, $sdata) = each %test) { print "got in the first one!\n"; while(my ($key2, $stats) = each %$sdata) { print "got here! key2 is $key2\n"; } } } else { ################################################# # this one always works for my $key1 (keys %test) { my $sdata = $test{$key1}; print "got in the first one!\n"; for my $key2 (keys %$sdata) { print "got here! key2 is $key2\n"; } } }

2005-10-01 Retitled by planetscape, as per Monastery guidelines
Original title: 'A myterious bug that arises under certain innocent-looking situation'

Replies are listed 'Best First'.
Re: mystery re each + last
by merlyn (Sage) on Sep 28, 2005 at 15:08 UTC
    From perldoc -f each:
    When the hash is entirely read, a null array is returne +d in list context (which when assigned produces a false (0) +value), and "undef" in scalar context. The next call to "each" + after that will start iterating again. There is a single ite +rator for each hash, shared by all "each", "keys", and "value +s" func- tion calls in the program; it can be reset by reading a +ll the elements from the hash, or by evaluating "keys HASH" or + "values HASH".
    So, your problem is that you're leaving the "single iterator" in the middle of the hash, and not resetting it. You'll need to reset it before entering the next loop.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      BTW, forgot to mention a derivative from the answer, and a complaint about this 'each' property:

      1. Since there's an iterator for each hash, if I had only used 'last' in the inner while loop, then only the inner while loop cannot be re-entered.

      2. Actually my script did not leave the iterator in the middle of the hash (it already read the only element in the hash before calling 'last'), it's just that 'each' can only be reset after calling 'each' ONE MORE time AFTER all elements were read. So the documentation above should be modified (because it incorrectly stated "it can be reset by reading all the elements from the hash", but my script did read all elements in the hash!).

      Well, just want to be picky, after wasting much time on this error-prone iterator for 'each'. :)

      A truly elegant and impeccably correct answer! I wish I could ++ you more than once for that one. The above is a fantastic example of how to answer factual questions: it provides the correct information, explains it briefly and in plain language, and explains how the information was found.

      Bravo, sir! Bravo!

      <-radiant.matrix->
      Larry Wall is Yoda: there is no try{} (ok, except in Perl6; way to ruin a joke, Larry! ;P)
      The Code that can be seen is not the true Code
      "In any sufficiently large group of people, most are idiots" - Kaa's Law
      Interesting. Thanks! I guess the lesson is never use each and last together ('cause one'd probably forget about resetting).