Niels Larsen
Niels Larsen

Reputation: 119

Perl scope function strange-ness?

#!/usr/bin/env perl

use strict;
use warnings FATAL => qw ( all );

my ( $func, @list, $num );

$func = sub {
    print $num // "undef"; print "\n";
};

@list = ( 1,2,3 );

foreach $num ( @list ) {
    $func->();
};

This piece of perl prints

undef
undef 
undef 

instead of

1
2
3

The $func routine can see @list, so why not $num?

Upvotes: 2

Views: 59

Answers (4)

Axeman
Axeman

Reputation: 29854

I could quote perlsyn, but perlsyn does not appear to say clearly what is the case. The variable is not "localized", and there is nothing that "regains" its value upon exiting the loop. The outer variable is hidden when you declare the loop variable, and visible after you're out of the loop.

It implicitly hides the variable in the outer scope, which retains its value.

The output from this code scrap will illustrate:

use 5.022;
use strict;
use warnings FATAL => qw ( all );
use Data::Dumper;

my ( $func, @list, $num );

$func = sub {
    print $num // "undef"; print "\n";
};
$num = 101;
$func->();

my $np = \$num;
print Data::Dumper->Dump( [ $np ], [ '*np' ] );

@list = ( 1,2,3 );

foreach $num ( @list ) {
    print Data::Dumper->Dump( [ $num ], [ '*num' ] );
    print Data::Dumper->Dump( [ $np ], [ '*np' ] );
    my $np2 = \$num;
    print Data::Dumper->Dump( [ $np2 ], [ '*np2' ] );
    print Data::Dumper->Dump( [ $np ], [ '*np' ] );
    $$np++;
    $func->();
}

Which outputs this:

101
$np = \101;
$num = 1;
$np = \101;
$np2 = \1;
$np = \101;
102
$num = 2;
$np = \102;
$np2 = \2;
$np = \102;
103
$num = 3;
$np = \103;
$np2 = \3;
$np = \103;
104

Thus, inside the loop, the symbol $num simply does not refer to the memory pointed to by $np, but instead at the memory pointed to by $np2.

Upvotes: 0

Niels Larsen
Niels Larsen

Reputation: 119

Well, guess the reason is this, from the docs:

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 localization occurs only in a foreach loop.

which means I should not use the foreach loop i guess, but for or while ..

Upvotes: 0

TLP
TLP

Reputation: 67900

You are accessing a localized version of the $num variable, like Sobrique says. What you intended was to use a reference to the variable $num. Which is what I show here:

my ( $func, @list, $num );
$func = sub {
    print $$num // "undef"; print "\n";
};

@list = ( 1,2,3 );

foreach ( @list ) {
    $num = \$_;
    $func->();
};

You can also use the global version of the variable, instead of the lexical:

my ( $func, @list, $num );

$func = sub {
    print $main::num // "undef"; print "\n";
};

@list = ( 1,2,3 );

foreach $main::num ( @list ) {
    $func->();
};

But this is a silly way to encapsulate this. You should not use global variables inside subroutines. This is not good practice. Instead, pass the value to the sub and access it via the @_ variable (in this case by the first array index $_[0]):

my $func = sub {
    print $_[0] // "undef"; print "\n";
};

my @list = ( 1,2,3 );

for ( @list ) {
    $func->($_);
};

I also fixed some idiomatic coding style above.

Upvotes: 1

Sobrique
Sobrique

Reputation: 53478

Because foreach loops implicitly localise their iterator variables.

See: perlsyn

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 localization occurs only in a foreach loop.

But really - this doesn't come up often, because it's really bad form to do any sort of messing around with a loop iterator from outside. Narrow down your scope, and pass variables around to avoid bugs and troubleshooting pain.

Upvotes: 4

Related Questions