develooper Front page | perl.perl5.porters | Postings from September 2016

Re: [perl #129757] for loop doesn't update correct variable

Thread Previous | Thread Next
From:
Aristotle Pagaltzis
Date:
September 29, 2016 04:01
Subject:
Re: [perl #129757] for loop doesn't update correct variable
Message ID:
20160929040054.GA30356@plasmasturm.org
* Mark-Jason Dominus <perlbug-followup@perl.org> [2016-09-29 03:00]:
> The following program:
>
>   my $i;
>   sub announce { printf "Count %d\n", $i }
>
>   for $i (1..3) {
>     announce();
>   }
>
> It emits
>
>   Count 0
>   Count 0
>   Count 0
>
> But it should be
>
>   Count 1
>   Count 2
>   Count 3

Maybe. But that might affect this:

    $ perl -E 'my @i; for my $i (5..8) { push @i, sub { say $i } }; $_->() for @i'
    5
    6
    7
    8

That behaviour must not break under any circumstances.

To make that work, in each iteration, foreach rebinds the name $i to the
scalar for that iteration:

    perl -E 'my @i = (5..8); for my $i (@i) { say \$i eq \$i[$a++] }'
    1
    1
    1
    1

It seems to me that this binding is necessarily lexical to the loop
block scope.

Meanwhile the `announce` sub has already closed over $i at compile time.

I don’t know that it’s possible to change either of these facts. Not to
mention doing it in such a way that closing over the loop variable at
runtime would remain unaffected.

The straightforward way to get your “correct” output would be to change
foreach to do local()-style value shadowing with the same scalar. Which
would break a huge amount of code, and I think it would generally be
a worse default, even though one consequence of the current behaviour is
the case you ran into here.

You can always use while() when you need it.

> Also, when correcting that bug, please make sure this is also corrected:
>
>   for my $i (1..3) {
>     sub announce { printf "Count %d\n", $i }
>     announce();
>   }
>
> It shows the same wrong behavior.

That sub is compiled just once despite its placement inside the loop and
it binds $i at compile time.

Arguably perl ought to warn about this, the same way that it warns when
you make the same mistake but you write it this way:

    sub do_announce {
      my $i = shift;
      sub announce { printf "Count %d\n", $i }
      announce();
    }

That throws the infamous “will not stay shared” warning. But you would
not want this to start warning:

    {
      my $count = 0;
      sub announce { printf "Count %d\n", $count }
    }

I don’t know how hard it would be to distinguish sure-fire warn-worthy
situations from benign ones here.

Regards,
-- 
Aristotle Pagaltzis // <http://plasmasturm.org/>

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About