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 outp
Try
foreach my $a (@_ = @ar)
now modifying $a does not modify @ar. Works for me on v5.20.2
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
.
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"'
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.
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;
}
}
$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.