Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Why does each() always re-evaluate its argument?

by Darkwing (Beadle)
on Dec 06, 2023 at 12:35 UTC ( [id://11156133]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks,

During development, I noticed a behavior of eval that I had not expected

use strict; use warnings; my %h = (a => 1, b => 2, c => 3); sub clone_h { print "cloning \%a\n"; return +{%h}; } while (my ($key, $val) = each(%{clone_h()})) { print("$key => $val\n"); }
This results in an infinite loop, printing
cloning %a b => 2 cloning %a c => 3 cloning %a a => 1 cloning %a c => 3 cloning %a a => 1 cloning %a a => 1 cloning %a b => 2 cloning %a c => 3 ...

Obviously, clone_h() is called again and again. Of course, I can work around this by first saving the result of clone_h() in a temp variable and then put the variable into the each(). But why does each() not just evaluate its argument and then work on the result?

I tried this with perl 5.10, 5.14 and 5.38.

But: The corresponding code using an array and foreach() works as expected:

use strict; use warnings; my @a = qw(a b c); sub clone_a { print "cloning \@a\n"; return [@a]; } foreach my $entry (@{clone_a()}) { print "$entry\n"; }

Why does each (but not foreach) it work like that? Why is a temporary variable forced (which seems redundant to me)?

2023-12-06 Retitled by Discipulus, as per Monastery guidelines
Original title: 'Why does eval() always re-evaluate its argument?'

Replies are listed 'Best First'.
Re: Why does each() always re-evaluate its argument?
by choroba (Cardinal) on Dec 06, 2023 at 12:44 UTC
    That's how each works. It works the same way on arrays:
    #! /usr/bin/perl use warnings; use strict; use feature qw{ say }; my @a = (a => 1, b => 2, c => 3); sub clone_a { print "cloning \%a\n"; return [@a] } while (my ($index, $val) = each @{ clone_a() }) { print "$index => $val\n"; }

    Perl stores the iterator for each in the hash or array structure. As the anonymous hash/array is a different one each time, it has a different iterator.

    for (or foreach for those who like to type more) just loops over a list of values. That's why you can for qw( a b c ) - no object needed to store an iterator.

    Update: BTW, where's any eval anywhere?

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      Thanks, the "eval", of course, was a typo.
        Please, fix it, so we don't confuse further readers.

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Why does each() always re-evaluate its argument? (Updated x2 - experimental "for_list" )
by LanX (Saint) on Dec 06, 2023 at 13:32 UTC
    It's not each , but the %{...} doing reevaluation.

    each expects a hash (or array with "newer" perls).

    The %{} does dereferencing of the enclosed expression, which needs to be evaluated.

    There is no way to tell for %{} if and why the result should be cached.

    Update

    To elaborate further, each is not a loop construct like foreach

    It's more like a method operating on the hash, with no clear entry point.

    Think %hash->each() and you can call each anytime outside while.

    Update

    You may rather want to take a look at this newer (5.36 experimental) syntax:

    use experimental "for_list"; foreach my ($key, $value) (%hash) { # iterate over the hash # The hash is immediately copied to a flat list before the loop # starts. The list contains copies of keys but aliases of values. # This is the same behaviour as for $var (%hash) {...} }

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

        > but, sadly, did not run appreciably faster

        experimental "for_list" solves so many issues of each , that I'm already very happy if it's not slower.

        It's elegant, orthogonal and intuitive, and I hope the experimental phase will be a success.

        This benchmarks claims it to be much faster, but we all know how tough it is to write reasonable benchmarks which don't compare apples with oranges.

        Actually I'm surprised that copying large hashes into a list can even compete with the built-in iterator-counter of perl hashes used by 'each'.

        The implemention must be very clever. Or the overhead will only show with much larger hashes.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        see Wikisyntax for the Monastery

Re: Why does each() always re-evaluate its argument?
by ikegami (Patriarch) on Dec 06, 2023 at 21:27 UTC

    It's not a difference between a foreach loop and each; it's not a difference between a foreach loop and a while loop.

    A foreach loop is used when we need to iterate a number of times which is known up front.

    On the other hand, a while loop allows us to loop a number of times that isn't known ahead of time. An expression is repeatedly evaluated to determine if the loop should be entered again. It MUST be evaluated each time. It wouldn't work otherwise.


    So why does following work:

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

    Each array and hash has an iterator associated with it which is used by each, keys and values. Each call to each fetches one element from that iterator (resetting the iterator once the end is reached).

    So then what about the following:

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

    A different, newly-created hash is passed to each each time. Each of those hashes has a fresh iterator. Each of those iterator returns the first element of that hash.


    Giving each array and hash its own iterator allows us to use

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

    instead of having to use

    my $iter = get_iter( \%hash ); while ( my ( $k, $v ) = $iter->() ) { ... }

    What some other newer languages have done instead is provide syntactical support for iterators. The equivalent of the previous snippet is what's executed.

    using ( var iter = dict.GetEnumerator() ) { while ( iter.MoveNext() ) { var entry = iter.Current; ... } }

    But a shorthand is provided by the language.

    foreach ( var entry in dict ) { ... }

    You can build iterators in Perl, but it lacks the syntactical support shown here.

Re: Why does each() always re-evaluate its argument?
by Darkwing (Beadle) on Dec 08, 2023 at 06:21 UTC
    Thanks to all for your answers and comments!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11156133]
Approved by marto
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (9)
As of 2024-04-19 16:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found