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

Hello all -

I've got a problem using the result of a function which has the following behaviour: When it is called in list context, it returns a hash; but when it is called in scalar context, it returns a reference to a hash. My problem relates to the second case, when the result of the function call is used in a 'while .... each ...' construct. Here is the example code:

use warnings; use strict; sub e() { my %result=(A=>1,B=>2); wantarray ? %result : \%result; } my %env=e; $|=1; while (my ($var,$val) = each %env) { print "(hash) $var=$val\n"; } while (my ($var,$val) = each %{ &e }) { print "(ref) $var=$val\n"; }
The first while works fine. %env was set by calling e in list context, and it is the hash. The second while loops forever, returning the same element of the hash all the time.

My reasoning was as follows: Inside %{...}, the call to e is done in a scalar context, so it should return the reference to the hash, which then would be passed to each. The call to each in turn is in list context, so we should iterated over the hash. However, it looks as if the iterator would be reset every time.

Could someone explain to me, what is going on her and why, and how to properly iterate over a hash given a hash reference? Well, I can of course use the keys function, but I wonder whether it is possible to do with each.

-- 
Ronald Fischer <ynnor@mm.st>

Replies are listed 'Best First'.
Re: This "each" goes to endless loop each time...
by moritz (Cardinal) on Jun 19, 2008 at 10:48 UTC
    The problem is that e() always returns a different hash reference, for which each always returns the "first" pair.

    You can work around that by memoizing e(), but I don't know if that's applicable for what you want to do:

    use warnings; use strict; use Memoize; memoize('e'); sub e() { my %result=(A=>1,B=>2); wantarray ? %result : \%result; } my %env=e; $|=1; while (my ($var,$val) = each %env) { print "(hash) $var=$val\n"; } while (my ($var,$val) = each %{ e() }) { print "(ref) $var=$val\n"; }

    P.S. Please don't use &e to call a sub, use e() instead - you might get surprising results otherwise.

    Update: or simply assign the ref to a temporary variable: my $e = e(); while (my ($k, $v) = each %$e){...}

      The problem is that e() always returns a different hash reference
      Ah, that's it! I understand.
      Please don't use &e to call a sub, use e() instead - you might get surprising results otherwise.
      Agreed. I had first %{ e }, but this was (of course) interpreted as hash variable %e, so I replaced it without much thinking by %{&e}. You are absolutely right that %{e()} would have been the better choice.
      -- 
      Ronald Fischer <ynnor@mm.st>
Re: This "each" goes to endless loop each time...
by GrandFather (Saint) on Jun 19, 2008 at 11:04 UTC

    The problem is evaluating e for each iteration of the while loop. If you make the ref assignment outside the loop things work as expected:

    use warnings; use strict; my %env= %{e ()}; $|=1; while (my ($var, $val) = each %env) { print "(hash) $var=$val\n"; } my $ref = e (); while (my ($var,$val) = each %$ref) { print "(ref) $var=$val\n"; } sub e { my %result=(A=>1,B=>2); wantarray ? each %result : \%result; }

    Prints:

    (hash) A=1 (hash) B=2 (ref) A=1 (ref) B=2

    As an aside: prototyping subs in Perl is only very occasionally required and should not be done as a matter of course. Likewise, calling subs using & is magical and should only be used when required.


    Perl is environmentally friendly - it saves trees
      As an aside: prototyping subs in Perl is only very occasionally required and should not be done as a matter of course
      Well, maybe I misinterpreted perlsub which says It's probably best to prototype new functions as being a recommendation for prototypes. I know that the basic idea in prototypes is to provide a way to call subs like "builtin" ones, but I must admit that I already found more than one errors in my code, because Perl complained that my calling parameters don't match the prototype. Of course I know that it easy to bypass the prototype checking if I want to (but usually I don't want to).
      -- 
      Ronald Fischer <ynnor@mm.st>

        The full quote is "It's probably best to prototype new functions, not retrofit prototyping into older ones" with the strong implication that it only applies in the context of adding prototypes to old subs. It is followed by a brief explanation of why retrofitting should be avoided: "That's because you must be especially careful about silent impositions of differing list versus scalar contexts". And that is the crux of the problem - prototyped subs don't do what you expect in some situations and often lead to very hard to see and debug problems. It is unfortunate that so much space in the documentation is given to a feature that should have a large "Here be dragons" label on it.

        Note too that "the intent of this feature is primarily to let you define subroutines that work like built-in functions" could (and according to informed opinion: should) be read to mean the only time prototypes should be used is to emulate built in functions.

        Note too the final comment in the prototype section of the documentation: "This is all very powerful, of course, and should be used only in moderation to make the world a better place.".

        Much has been written about prototype at PerlMonks. Some of the following may be interesting: subroutine prototypes still bad?, The purpose of prototypes in perl subroutines, Considering Prototypes, Are prototypes evil? and many others.


        Perl is environmentally friendly - it saves trees
Re: This "each" goes to endless loop each time...
by tfoertsch (Beadle) on Jun 19, 2008 at 11:19 UTC
    The state of the each operator is bound to a single hash. In your 2nd case you call &e for each iteration. That yields a new hash each time. So the each operator sees a new hash in every iteration. You may try a for loop:
    for( my $h=&e; my ($k, $v)=each %$h; ) {...}