views:

295

answers:

5

In Perl, does using 'my' within a foreach loop have any effect? It seems that the index variable is always local whether or not 'my' is used. So can you drop the 'my' within the foreach loop and still have private scope within the body of the loop?

As can be seen, using the 'for' loop there is a difference between using / not using 'my':

use strict; 
use warnings; 

my ($x, $y) = ('INIT', 'INIT'); 

my $temp = 0; 

for ($x = 1; $x < 10; $x++) {
 $temp = $x+1; 
}

print "This is x: $x\n";   # prints 'This is x: 10'. 

for (my $y = 1; $y < 10; $y++) {
 $temp = $y+1; 
}

print "This is y: $y\n";   # prints 'This is y: INIT'. 

But on foreach it does not seem to have an effect:

my ($i, $j) = ('INIT', 'INIT'); 

foreach $i (1..10){
    $temp = $i+1;
}

print "\nThis is i: $i\n";   # prints 'This is i: INIT'. 



foreach my $j (1..10){
    $temp = $j+1;
}

print "\nThis is j: $j\n";   # prints 'This is j: INIT'. 
A: 

One of the most painful discoveries about Perl I made was that the "my" variable on a foreach loop is not a local copy. I found myself frustrated to discover my array was being trashed.

The only way around seems to be:


foreach ( @array ) {
  my $element = $_; # make a local copy
  ...
}

If you look at perldoc it states: "the foreach loop index variable is an implicit alias for each item in the list that you're looping over" and "If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop".

PP
+1 interesting and helpful, -1 not relevant to this question
mobrule
You're right. Leaving the answer anyway as it was quite a discovery for me.
PP
It sucks if you don't expect it. But it can also be very handy. For example, if you need to apply a transformation to all the members of an array: `s/^\s+// for @foo;` The same thing applies to sub arguments, plus `map` and `grep`. BTW, `List::MoreUtils` has `apply` which is like `map` but works with a local copy, so you can do `my @bar = apply { s/Q/q/g } @foo;` without modifying `@foo`.
daotoad
The use of `for` you mentioned here, where `$_` refers to the actual object in the list, is easy to understand. However when saying `for my $var ( @array )` it is easy for the novice to assume that `$var` is a local copy of the array element.
PP
+12  A: 

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

The foreach loop iterates over a normal list value and sets the variable VAR to be each element of the list in turn. If the variable is preceded with the keyword my, then it is lexically scoped, and is therefore visible only within the loop. Otherwise, the variable is implicitly local to the loop and regains its former value upon exiting the loop. If the variable was previously declared with my, it uses that variable instead of the global one, but it's still localized to the loop. This implicit localisation occurs only in a foreach loop.

Hans W
From original poster. Just wanted to add a synthesis to be helpful to other people new to perl like me. If anything is wrong, let me know and I will clean this up. The terms 'implicit local/localisation' above are not just qualitative terms: in fact 'local' here is an exact technical Perl language construct. 'foreach' uses the dynamic 'local' scope as opposed to the lexical 'my' scope or global 'package' scope. A nice link that helped me with these 3 scopes http://perl.plover.com/FAQs/Namespaces.html.Before, I was confused - though 'local' in perl docs was same as lexical 'my'.
A: 

I just did some extra research on this interesting issue.

foreach doesn't care if you do "my $y;" before the loop either. The iterator will still only change within the scope:

my $y;
foreach $y (1..10) {}

The $y will change within the foreach scope, but when it leaves, it reverts back to whatever it was before the loop.

I had thought that perhaps foreach automatically does a "my $y;" before it starts running, so I tried strict mode, but that explanation doesn't cut it because then it still demands:

foreach my $y (1..10) {}

as opposed to:

foreach $y (1..10) {}

...which one would think weren't necessary if "foreach $y" was functionally the same as "foreach my $y".

Helgi Hrafn Gunnarsson
+10  A: 

Hans linked to the documentation describing how the variable in the foreach loop is scoped. The distinction is subtle but it could be important:

sub f { return $i }
$i = 4;
$m = $n = 0;

foreach    $i (1 .. 10) { $m += f() }
foreach my $i (1 .. 10) { $n += f() }

print "Result with localization:    $m\n";    #  ==>  55
print "Result with lexical scoping: $n\n";    #  ==>  40
mobrule
Your post confused the hell out of me. I expected `$m` to be 55, and `$n` to be 40. I was confused until I ran the script. I think you've got an error in the comments.
daotoad
Sorry @daotoad, bug in the comments.
mobrule
A: 

The iterator in a foreach loop is effectively local to the loop. That said, it does directly hold the value from the list in what is effectively a call by reference as opposed to by value. Thus, if you operate on the value while in the loop it will change the value in the list. However, the iterator variable is only defined in reference to the loop:

@names = qw(Kirk Spock Bones);

$officer = "Riker";
print "@names $officer\n"; # will print Kirk Spock Bones Riker

foreach $officer (@names) {
     $officer .= "y";
}

print "@names $officer\n"; # will print Kirky Spocky Bonesy Riker

So, the iterator, in this case $officer, is effectively preceded by a 'my' in terms of retaining any value it had before the foreach loop. However, if the values it holds is changed that change will pass back to the list you are iterating over.

HerbN