views:

1596

answers:

3

I don't need this, obviously; I'm just curious about what's going on here. Am I missing something simple? Can I rely on this behaviour in all versions of Perl?)

Perl v5.8.8:

%h = ( 0=>'zero', 1=>'one', 2=>'two' );
while ($k = each %h) {
    $v = delete $h{$k};
    print "deleted $v; remaining: @h{0..2}\n";
}

outputs

deleted one; remaining: zero  two
deleted zero; remaining:   two
deleted two; remaining:

man perlfunc (each) does not explain why the while loop continues when $k is assigned 0. The code behaves as if the condition on the while loop were ($k = each %h, defined $k).

If the loop condition is actually changed to ($k = each %h, $k) then it does indeed stop at $k = 0 as expected.

It also stops at $k = 0 for the following reimplementation of each:

%h = ( 0=>'zero', 1=>'one', 2=>'two' );
sub each2 {
    return each %{$_[0]};
}
while ($k = each2 \%h) {
    $v = delete $h{$k};
    print "deleted $v; remaining: @h{0..2}\n";
}

outputs just:

deleted one; remaining: zero  two
+27  A: 

You're calling each in scalar context, so it's not working because of a list return value.

Just like

while ($line = <FILE>)

is special-cased to add an implicit defined, so is

while ($key = each %hash)

In 5.8.8, that happens in op.c, lines 3760-3766:

case OP_SASSIGN:
  if (k1->op_type == OP_READDIR
      || k1->op_type == OP_GLOB
      || (k1->op_type == OP_NULL && k1->op_targ == OP_GLOB)
      || k1->op_type == OP_EACH)
    expr = newUNOP(OP_DEFINED, 0, expr);
  break;

I'm not sure if this applies to all versions of Perl 5.

See also: When does while() test for defined vs truth on PerlMonks. I can't find where this is mentioned in the Perl docs (the <FILE> case is mentioned, but I don't see the each case).

cjm
A: 

Thanks, cjm. It was clear some kind of implicit addition of a defined was going on like that for glob but not where it was documented. Now at least I know the limited cases in which that special handling applies.

But the information should be in the perlfunc documentation, not just the Perl source code!

+5  A: 

cjm is right. I just want to add that when coming up against strange things like this its often helpful to run your code through B::Deparse to see how Perl understood your code. I like to use the -p switch to show precedence mistakes, too.

$ perl -MO=Deparse,p your_example.plx
(%h) = (0, 'zero', 1, 'one', 2, 'two');
while (defined($k = each %h)) {
    $v = delete $h{$k};
    print "deleted $v; remaining: @h{0..2}\n";
}
your_example.plx syntax OK
Schwern