Another reason not to use each()

So I’ve learned my lesson. Do not use each().

Always good to read the blogs…

https://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html

Here’s a fun one that caused perl to go into an infinite loop. I suppose being frugal and not assigning things to variables is not necessarily a good idea.

  #!/usr/bin/perl

  use strict;
  use warnings;

  my %thingies = %{{qw/a 1 b 2 c 3/}};

  my $c=0;

  #while (my ($a,$b) = each %thingies ) {
  while (my ($a,$b) = each %{{qw/a 1 b 2 c 3/}} ) {
    print "$a $b $c\n";
    last if ++$c > 4;  # stop the madness
  }

Uncommenting out the line above where each uses a pre-assigned version of the same data element works fine. Not knowing anything regarding the internals of Perl makes me scratch my head. I’m sure there is a perfectly good explanation for this and maybe it has even been corrected in a more recent version of Perl?

$ perl -v

This is perl, v5.10.1 (*) built for x86_64-linux-thread-multi

Follow-up

Reading the PerlMonks helps too!

http://www.perlmonks.org/?node_id=754380

I guess perl does not read minds all of the time. In the tradition of do what I want, not what I wrote I had hoped that perl knew that I only wanted to create the data element once, not each time through the loop. My bad.

Moral: Do not use each() - unless you are aware of all of the gotchas and even them don’t use each.

3 Comments

I think the issue here is that `each` expects a hash argument - and you're passing it a list.

The more usual method of assinging to a hash would be:

# assign a list to a hash variable
my %thingies = qw/a 1 b 2 c 3/;

What you're doing is:

# turn a list into a hash-reference
{qw/a 1 b 2 c 3/}
# dereference hash reference back to a list
%{{qw/a 1 b 2 c 3/}}

You can see that you're passing a list to `each` by using Data::Dumper

use Data::Dumper;
my %x=qw/ a 1 b 2 c 3 /;

# inspect a named hash variable
print Dumper(\%x);

# inspect a list
print Dumper(qw/ a 1 b 2 c 3 /);

# turns out to be a list
print Dumper( %{{qw/ a 1 b 2 c 3 /}} );

outputs:

$VAR1 = {
'c' => '3',
'a' => '1',
'b' => '2'
};

$VAR1 = 'a';
$VAR2 = '1';
$VAR3 = 'b';
$VAR4 = '2';
$VAR5 = 'c';
$VAR6 = '3';

$VAR1 = 'c';
$VAR2 = '3';
$VAR3 = 'a';
$VAR4 = '1';
$VAR5 = 'b';
$VAR6 = '2';

I know that you can pass a hashref to `each` by doing:

while ( my ($k,$v) = each %$hashref ) {...}

which appears to be the same thing - but I think that must be special-cased in the parser, as I can't figure out any way of passing a hash to `each` which isn't assigned to a named hash or hash-ref variable.

fireartist: I’m afraid everything you said is wrong, and your entire analysis is mistaken.

In the best case, it would be redundant with bigfoot’s own analysis, who had already identified the problem correctly in the followup section of the post.

Here’s a thought experiment for what you’re missing: how come push @foo, $bar pushes something onto @foo?

Why doesn’t that pass the contents of @foo to push?

I think that must be special-cased in the parser

You’re pretty close… outside the fact that the case isn’t that special. Here’s a little challenge:

sub try_calling_me (\%) { warn $_[0] }
sub or_me (\@) { warn $_[0] }

(The answer to what’s going on is in perldoc perlsub.)

Leave a comment

About bigfoot

user-pic I blog about Perl and Bedrock.