Why doesn't Perl's foreach require its variable to be declared with my?
Asked Answered
A

2

12

Today, I stumbled over something in Perl I was not aware of: it "localizes" the variable that the elements of the list iterated over is assigned to.

This, of course, is documented in the Perl documentation - however I failed to remember or read it.

The following script demonstrates what I mean:

use warnings;
use strict;

my $g = 99;

foreach $g (1..5) {
  p($g);
}

sub p {
  my $l = shift;
  printf ("%2d %2d\n", $g, $l);
}

The script prints

99  1
99  2
99  3
99  4
99  5

because $g is "localized" to the foreach loop.

As far as I can tell there is no difference if I had added my to $g in the foreach loop:

foreach my $g (1..5) {

Actually, I ended up doing it because I feel it makes it clearer that the variable is local to the loop.

My question is now: is there a scenario where my using my does make a difference (given that $g is already declared globally).

Ahrendt answered 15/2, 2017 at 7:51 Comment(3)
The foreach does not require the my declaration if you have it declared globally. It is strange however that it will not complain that you are declaring it again. For instance if you remove the range, it will complain that you have already declared $g I will go and research this a bit.Epiphyte
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 localization occurs only in a foreach loop. perldoc.perl.org/perlsyn.html#Foreach-LoopsSought
@Sought That is correct, but if you take that code and remove the range it will create an error. The variable is not yet inside the loop, it is part of the foreach statement.Epiphyte
C
8

The investigated behavior is documented in Foreach Loops in perlsyn

The foreach loop iterates over a normal list value and sets the scalar 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.

which continues to the explanation

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.

Thus there should be no difference between localizing it with my or leaving that to foreach.

A little curiosity is that

This implicit localization occurs only in a foreach loop.

All this is further clarified in this snippet from Private Variables via my() from perlsub

The foreach loop defaults to scoping its index variable dynamically in the manner of local. However, if the index variable is prefixed with the keyword my, or if there is already a lexical by that name in scope, then a new lexical is created instead.

Since a new lexical is created inside in both cases there cannot be any practical difference.

I absolutely support and recommend (always) having a my there.

Camarillo answered 15/2, 2017 at 8:43 Comment(2)
That is understood. The problem I see here though is that $g is not yet within the loop. so if $g is not declared and assigned a value before, then $g is in fact an empty value.Epiphyte
@Gerry In foreach my $i (1..4) { } the scope of $i is restricted to the loop. It does not exist "before". The same is without my, if $i was declared before -- it is localized to the loop's scope as soon as it is seen. This is what the quote(s) above inform us.Camarillo
R
0

As per @zdim, this code:

use strict;    
my $g;    
foreach $g (1..5) {
  p($g);
}

Is functionally equivalent to this code:

use strict;    
my $g;    
foreach my $g (1..5) {
  p($g);
}

Note however, that this code produces an error:

use strict;    
foreach $g (1..5) {
  p($g);
}
my $g;    

To get the implicit declaration, you first have to have an an explicit declaration. This implemented as designed. Or at least, this is implemented as per documentation. It still feels like a bug to me. If it isn't going to use the existing declaration, I'd prefer it it threw an undefined symbol error.

You can(*) prevent this behaviour by using our instead of my.

For example, this code:

use strict;
our $g = 99;
foreach $g (1..5) {
  p($g);
}
printf ("%2d\n", $g);
sub p {
  my $l = shift;
  printf ("%2d %2d\n", $g, $l);
}

Produces this output:

 1  1
 2  2
 3  3
 4  4
 5  5
99

The original value of $g is still restored after the loop, but now $g is the same variable inside the loop and outside the loop.

(*) Just because you can does not mean you should.

Refit answered 13/6 at 17:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.