It's wrong because the function each has global side-effects: it has a global pointer for each hash it works on, and each time the method is called the loop will continue from the position where it left off previously. As such, at some point it will start after the last "stuff" we want to find and it will say the hash/object has none, though it may be full of it.sub find_stuff { my $self = shift; while (my $k = each %$self) { return $k if ($self->{$k}->{blablah}); } return undef; }
I was just bitten by this behaviour in a code that does dependency checks for RPMs — dependencies were being missed and packages failing installation due to missing dependencies.
Clearly Perl needs a way to reset each's pointer (can't be done except via keys, which moots the point). The loop in question was previously done with foreach/keys and I changed it to use each because foreach+keys were so unbearably slow on big hashes — it made a difference from waiting half a second after clicking the checkbox to install a package for the dependencies to be calculated to an almost instantaneous response when each was used.
A good optimization on foreach's performance wouldn't hurt either.
For now I'm having to do with this horrible workaround:
I could also have let the loop run to the end, putting a "next if $done" line in it, but that won't give me any guarantees either — it will leave the hash ready to be read again but fails if it receives the hash after it was read incompletely by some other code.sub find_stuff { my $self = shift; my $first = undef; while (1) { my $r = each %$self; unless ($r) { $r = each %$self; last unless defined $r; } last if defined ($first) && $first eq $r; $first = $r unless defined $first; # do stuff quitting the loop at will } return undef; }
ESC[78;89;13p ESC[110;121;13p
In reply to Optimization, side-effects and wrong code by cbraga
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |