Perl bites

darobin on 2002-01-25T17:52:08

I've been using Perl daily for five years and I think I know it fairly well (apart of course from some obscure filehandle magic or hidden things like PROPAGATE). Still, once in a while I get bitten but something that I didn't quite ignore, but that I clearly wasn't sufficiently aware of.

Today I got bitten rather bad. I have this simple code in XML::NamespaceSupport that iterated over a hash, and if the value matched a given pattern would immediately return the key (for those interested, this was in getPrefix() or get_prefix() depending on your coding preferences). Grant McLean reported a strange bug: when calling that method twice in a row, the second call would most of the time return nothing. A rather weird thing to do for a piece of code that just looks inside a hash... Here a (simplified) version of the offending code (does anyone have code2usePerlPRE script ?):

 while (my ($k, $v) = each %{$self->{someHash}}) {
     return $k if foo($v);
 }

It seems pretty innocent at first, but those of you with acute eyes will have spotted the problem: each() uses an iterator to know where it is between two calls. That iterator, is only reset when it reaches the end of the hash. But here, unless there was a failure to match the condition, the end of the hash wasn't reached. So on the first call for a given pattern it'd match, but on the second call it'd start at the pair in the hash following the one that had previously matched. If that one failed, the next call would succeed again.

I don't think that this is a problem with each() as there are good reasons for it to be so. However, one has to admit that it makes it rather easy to create a subtle bug. Perhaps a docpatch is in order?


each

jdavidb on 2002-01-25T19:34:00

I never use each. Originally the reason was that it was just so different from the way I thought that I couldn't even understand it. Fortunately TMTOWTDI. Now, though, I've seen many, many people get bitten by each, especially newcomers. So I don't do it.

My body of code is full of:

foreach my $key (%hash) { print "$key:\t$hash{$key}\n"; }

and similar constructs.

Re:each

darobin on 2002-01-25T20:05:38

Yes, I'm switching a fair amount to for(each) now just to be on the safe side, only leaving each() when I know that I want it.

I didn't use each() all that much. Mostly it was for cases when I was certain that I was going to want both the key and the value no matter what. I find it far more elegant and readable than the hash access syntax :-)

Re:each

jdavidb on 2002-01-26T23:28:24

I can read it now, but it just really made no sense to me at all when I first saw it. I was having trouble enough understanding the whole hash concept, I think. When people throw C<each> at a beginner, I don't see how they can handle it.

Some Perl trainer commented on a p6 list once that after discovering many students were confused initially by using $ for single array and hash elements (why is it $hash{key} instead of %hash{key} and $array[$key] instead of @array[$key]), he was finding it easier to teach them about accessing each element first before teaching them how to use @ and % to get the whole array or hash or a slice. Then @ or % can be taught as meaning "the whole thing."

Of course, since Perl 6 is going to change all that I suppose it won't matter. I wonder what will happen to C<each>.

each

TorgoX on 2002-01-25T21:21:21

My perldoc -f says:
There is a single iterator for each hash, shared by all each, keys, and values function calls in the program; it can be reset by reading all the elements from the hash, or by evaluating keys HASH or values HASH.
So before I start a loop over each, I always have a line:
keys %thing; # reset hash iterator

Or:

keys %{$self->{tiny_pies}}; # reset hash iterator

Re:each

pudge on 2002-01-25T21:48:38

Is keys in void context optimized to do nothing aside from resetting the iterator? Although, I guess most hashes won't be a problem for efficiency, unless they are tied hashes, in which case you might not want such an optimization ...

Re:each

TorgoX on 2002-01-25T22:09:45

Is keys in void context optimized to do nothing aside from resetting the iterator?

I don't know.

Re:each

chromatic on 2002-01-26T02:29:53

If I'm looking in the right place (doop.c, function Perl_do_kv), it does (a call to hv_iterinit comes before the context check). I'm at patchlevel 14388.