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


in reply to Re: Lexical closures
in thread Lexical closures

The "my $i" outside the loop does not declare a lexical variable that is used as the the loop variable in the way you think it does. Perl aliases the loop variable to each element in the for list as they are iterated over so the closure is not over $i, but over the aliased list elements. Consider:

use strict; use warnings; my @flist; my @array = 0 .. 3; for my $elt (@array) { push @flist, sub {return $elt;}; } print $_->(), "\n" for @flist; @array[0 .. 3] = 10 .. 13; print $_->(), "\n" for @flist;

Prints:

0 1 2 3 10 11 12 13

Perl reduces RSI - it saves typing

Replies are listed 'Best First'.
Re^3: Lexical closures
by backstab (Novice) on Oct 25, 2008 at 11:08 UTC

    Yes! Thanks. I've never think about aliasing longer than the easy way to work on the elements of an array directly, that is the purpose, in most case, of the foreach loop.

    But, I'm continuing playing with that and realize aliasing also implies narrowing the scope of the variable to the loop,

    my $i = 123; foreach $i (0..3) { ; } print "$i\n"; # 123

    No? If it does not narrow it itself the final value of $i should be 3.

    If so, the funny thing is in "foreach my $i" my is not revelant as far we do not control in fact the scope of $i at this point. So, can we consider a bug, or at least an over sanity warn, the fact strict.pm forces us to put it here?

      It's not a bug; it was a deliberate design decision in (I believe) 5.004. Besides, you don't have to put my there. The aliasing works with global variables too:

      use 5.010; package Foo; use vars '$i'; use strict; use warnings; sub bar { for $i (1 .. 10) { main::baz( $i ); } } package main; use strict; use warnings; use Test::More tests => 12; $Foo::i = 100; sub baz { my $expect = shift; is( $Foo::i, $expect ); } baz(100); Foo::bar(); baz(100);
        I think we can also show how the aliased variable can be global with the following,
        use vars '$i'; $i = 123; sub foo { my $arg = shift; print "$arg, $i\n"; } foreach $i (0..2) { foo($i); } foo('x'); # obviously alias works only within the loop # Prints, # 0,0 # 1,1 # 2,2 # x,123
        If so, I have a question related to my scope. Within the previous example let's declared $i with my instead.
        my $i; $i = 123; sub foo { my $arg = shift; print "$arg, $i\n"; } foreach $i (0..2) { foo($i); } foo('x'); # Prints, # 0,123 # 1,123 # 2,123 # x,123

        At this point I'm confused in fact. Should not we get the same result? Does not my scope to nested blocks as well?

        It looks like aliasing does not affect my'd variable despite to be within the same scope or nested scope. The previous result is what I have expected using my on the foreach itself.

      I invariably write:

      for my $i (0..3) {

      so I am reminded of the scope of the loop variable and am not tempted to think of it having larger scope.

      A very good rule to follow is to always declare lexical variables in the smallest scope possible which precludes declaring declaring loop variables outside the for loop scope


      Perl reduces RSI - it saves typing

        Using my on for/foreach loop variables is not merely making explicit the implicit localization that is provided by the loop: the new lexical variable is distinct from any variable of the same name (lexical or not) in the surrounding scope.

        Note the difference in the following example:

        #!/usr/bin/perl use strict; use warnings; use vars qw($n); $n = "global"; sub foo { my $x = shift; print "$x, $n\n"; } foreach $n (0..2) { foo($n); } print "\$n = $n\n"; foreach my $n (0..2) { foo($n); } print "\$n = $n\n";

        Which produces:

        0, 0 1, 1 2, 2 $n = global 0, global 1, global 2, global $n = global

        After both foreach loops the global $n remains unchanged but within the first (my not used) the global is affected while within the second (my is used) the global is not affected.

        I admit in day-to-day scripts always wanting the foreach element placeholder to be local (in the general sense) to the loop so that having it my'd as far is mandatory as chromatic showed us in the other post.

        But, we can imagine some uses in special cases as debugging, introspection or understanding dead code...

        I did a quick grep on the perl files I have installed for foreach that seems to use a global "$i". I've only found one, maybe, from perl5db.pl.

        It might be an interessing read.