views:

299

answers:

5

So, I never knew this and I want to get some clarifcation on it. I know if you do

foreach (@list){

if you change $_ in that loop it will affect the actual data. But, I did not know that if you did

foreach my $var1 (@list){

If you changed $var1 in the loop it would change the actual data. :-/ So, is there a way to loop over @list but keep the variable a read-only copy, or a copy that if changed will not change the value in @list?

+6  A: 

Easiest way is just to copy it:

foreach my $var1 (@list) {
    my $var1_scratch = $var1;

But if $var1 is a reference, $var1_scratch will be a reference to the same thing. To be really safe, you'd have to use something like Storable::dclone to do a deep copy:

foreach my $var1 ( @{ Storable::dclone( \@list ) } ) {
}

(untested). Then you should be able to safely change $var1. But it could be expensive if @list is a big datastructure.

ysth
Be careful, $var1 is not a reference, but an alias. See the difference :my $var2=\$list[0]; print "$var2\n";displays SCALAR(0x8171880)
wazoox
@wazoox: He meant if $var1 contained a reference, e.g. if @list contained a bunch of HASH refs. Even if you break the aliasing, you still have another copy of a reference to the same thing.
Michael Carman
+6  A: 

$var is aliased to each item in turn.

See http://perldoc.perl.org/perlsyn.html#Foreach-Loops

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.

Sinan Ünür
+1  A: 

I don't know how to force the variable to be by-value instead of by-reference in the foreach statement itself. You can copy the value of $_ though.

#!/usr/perl/bin
use warnings;
use strict;

use Data::Dumper;

my @data = (1, 2, 3, 4);

print "Before:\n", Dumper(\@data), "\n\n\n";

foreach (@data) {
    my $v = $_;
    $v++;
}

print "After:\n", Dumper(\@data), "\n\n\n";

__END__
Sukotto
Where you say `my $v = shift;` you mean `my $v = $_;`.
Dave Hinton
@Dave Hinton corrected the error. @Sukotto try printing $v in the body of your loop as you originally wrote it to see your error.
Sinan Ünür
Yes, I meant `my $v = $_;` :-(
Sukotto
+3  A: 

It is an alias, not a reference. If you want to create your own aliases (outside of for) you can use Data::Alias.

Chas. Owens
Umm... +0. That's +1 for the alias/reference clarification and -1 for giving instructions on doing the opposite of what the OP asked for. (Creating an alias as opposed to breaking aliasing.)
Michael Carman
@Michael Carman ysth had already given the right answer, I was just pointing out that it is possible to create your own aliases.
Chas. Owens
A: 

The only difference between these loops:

foreach (@array) { ... }
foreach my $var (@array) { ... }

is the loop variable. The aliasing is a function of foreach, not the implicit variable $_. Note that this is an alias (another name for the same thing) and not a reference (a pointer to a thing).

In the simple (and common) case, you can break the aliasing by making a copy:

foreach my $var (@array) {
    my $copy = $var;
    # do something that changes $copy
}

This works for ordinary scalar values. For references (or objects) you would need to make a deep copy using Storable or Clone, which could be expensive. Tied variables are problematic as well, perlsyn recommends avoiding the situation entirely.

Michael Carman
or Clone::More or Clone::Fast
ysth