开发者

Why is Perl foreach variable assignment modifying the values in the array?

OK, I have the following code:

use strict;
my @ar = (1, 2, 3);
foreach my $a (@ar)
{
  $a = $a + 1;
}

print join ", ", @ar;

and the 开发者_开发知识库output?

2, 3, 4

What the heck? Why does it do that? Will this always happen? is $a not really a local variable? What where they thinking?


Perl has lots of these almost-odd syntax things which greatly simplify common tasks (like iterating over a list and changing the contents in some way), but can trip you up if you're not aware of them.

$a is aliased to the value in the array - this allows you to modify the array inside the loop. If you don't want to do that, don't modify $a.


See perldoc perlsyn:

If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop. Conversely, if any element of LIST is NOT an lvalue, any attempt to modify that element will fail. In other words, the foreach loop index variable is an implicit alias for each item in the list that you're looping over.

There is nothing weird or odd about a documented language feature although I do find it odd how many people refuse check the docs upon encountering behavior they do not understand.


$a in this case is an alias to the array element. Just don't have $a = in your code and you won't modify the array. :-)

If I remember correctly, map, grep, etc. all have the same aliasing behaviour.


As others have said, this is documented.

My understanding is that the aliasing behavior of @_, for, map and grep provides a speed and memory optimization as well as providing interesting possibilities for the creative. What happens is essentially, a pass-by-reference invocation of the construct's block. This saves time and memory by avoiding unnecessary data copying.

use strict;
use warnings;

use List::MoreUtils qw(apply);

my @array = qw( cat dog horse kanagaroo );

foo(@array);


print join "\n", '', 'foo()', @array;

my @mapped = map { s/oo/ee/g } @array;

print join "\n", '', 'map-array', @array;
print join "\n", '', 'map-mapped', @mapped;

my @applied = apply { s/fee//g } @array;

print join "\n", '', 'apply-array', @array;
print join "\n", '', 'apply-applied', @applied;


sub foo {
   $_ .= 'foo' for @_;
}

Note the use of List::MoreUtils apply function. It works like map but makes a copy of the topic variable, rather than using a reference. If you hate writing code like:

 my @foo = map { my $f = $_; $f =~ s/foo/bar/ } @bar;

you'll love apply, which makes it into:

 my @foo = apply { s/foo/bar/ } @bar;

Something to watch out for: if you pass read only values into one of these constructs that modifies its input values, you will get a "Modification of a read-only value attempted" error.

perl -e '$_++ for "o"'


the important distinction here is that when you declare a my variable in the initialization section of a for loop, it seems to share some properties of both locals and lexicals (someone with more knowledge of the internals care to clarify?)

my @src = 1 .. 10;

for my $x (@src) {
    # $x is an alias to elements of @src
}

for (@src) {
    my $x = $_;
    # $_ is an alias but $x is not an alias
}

the interesting side effect of this is that in the first case, a sub{} defined within the for loop is a closure around whatever element of the list $x was aliased to. knowing this, it is possible (although a bit odd) to close around an aliased value which could even be a global, which I don't think is possible with any other construct.

our @global = 1 .. 10;
my @subs;
for my $x (@global) { 
    push @subs, sub {++$x}
}

$subs[5](); # modifies the @global array


Your $a is simply being used as an alias for each element of the list as you loop over it. It's being used in place of $_. You can tell that $a is not a local variable because it is declared outside of the block.

It's more obvious why assigning to $a changes the contents of the list if you think about it as being a stand in for $_ (which is what it is). In fact, $_ doesn't exist if you define your own iterator like that.

foreach my $a (1..10)
    print $_; # error
}

If you're wondering what the point is, consider the case:

my @row = (1..10);
my @col = (1..10);

foreach (@row){
    print $_;
    foreach(@col){
        print $_;
    }
}

In this case it is more readable to provide a friendlier name for $_

foreach my $x (@row){
    print $x;
    foreach my $y (@col){
        print $y;
    }
}


Try

foreach my $a (@_ = @ar)

now modifying $a does not modify @ar. Works for me on v5.20.2

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜