Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For Loop and Lexically Scoped Variables

Tags:

perl

Version #1

use warnings;
use strict;

my $count = 4;

for $count (1..8) {
    print "Count = $count\n";
    last if ($count == 6);
}

if (not defined($count)) {
    print "Count not defined\n";
}
else {
    print "Count = $count\n";
}

This prints:

1
2
3
4
5
6
4

Why? Because the for loop creates its own lexically scoped version of $count inside its block.

Version #2

use warnings;
use strict;

my $count;
for $count (1..8) {
    print "Count = $count\n";
    last if ($count == 6);
}

if (not defined($count)) {
    print "Count not defined\n";
}
else {
    print "Count = $count\n";
}

1
2
3
4
5
6
Count not defined

Whoops! I wanted to capture the exit value of $count, but the for loop had it's own lexically scoped version of $count!. I just had someone spend two hours trying to track down this bug.

Version #3

use warnings;
use strict;

for $count (1..8) {
    print "Count = $count\n";
    last if ($count == 6);
}

print "That's all folks!\n";

This gives me the error Global symbol "$count" requires explicit package name at line 5. But, I thought $count was automatically lexically scoped inside the for block. It seems like that only occurs when I've already declared a lexically scoped version of this variable elsewhere.

What was the reason for this behavior? Yes, I know about Conway's dictate that you should always use my for the for loop variable, but the question is why was the Perl interpretor designed this way.

like image 325
David W. Avatar asked Dec 03 '22 10:12

David W.


2 Answers

In Perl, assignment to the variable in the loop is always localized to the loop, and the loop variable is always an alias to the looped over value (meaning you can change the original elements by modifying the loop variable). This is true both for package variables (our) and lexical variables (my).

This behavior is closest to that of Perl's dynamic scoping of package variables (with the local keyword), but is also special cased to work with lexical variables (either declared in the loop or before hand).

In no case though does the looped over value persist in the loop variable after the loop ends. For a loop scoped variable, this is fairly intuitive, but for variables with scope beyond the loop, the behavior is analogous to a value localized (with local) inside of a block scope created by the loop.

for our $val (1 .. 10) {...} 

is equivalent to:

our $val;
my @list = 1 .. 10;
my $i = 0;

while ($i < @list) {
   local *val = \$list[$i++];
   # loop body
}

In pure perl it is not possible to write the expanded lexical version, but if a module like Data::Alias is used:

my $val;
my @list = 1 .. 10;
my $i = 0;

while ($i < @list) {
   alias $val = $list[$i++];
   # loop body
}
like image 112
Eric Strom Avatar answered Dec 25 '22 07:12

Eric Strom


Actually, in version #3 the variable is "localized" as opposed to lexically scoped.

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.

In any case, you will not be able to access the loop variable from that stlye of for-loop outside the loop. But you could use the other style (C-style) for-loop:

my $count;
for ($count=1; $count <= 8; $count++) {
    last if $count == 6;
}
...   # $count is now 6.
like image 29
mob Avatar answered Dec 25 '22 06:12

mob