1

I am confused with the behavior difference between the following two code segments:

$x = 8;
sub a {
    sub b {
        print "B: $x\n";
    }
    my $x = 3;
    &b();
}
&a();

and

$x = 8;
sub a {
    my $x = 3;
    sub b {
        print "B: $x\n";
    }
    &b();
}
&a();

While the first one outputs B:8, the second one outputs B:3. Shouldn't they output the same value, as the outer scope of sub b should be sub a? How does the different placements of functions make a difference?

gokhansim
  • 123
  • 6
  • 5
    `use warnings; use diagnostics;` – mob Apr 02 '18 at 14:54
  • 1
    It matters where `my` variable is defined, as it's visible in sub/block/etc. only after definition (no hoisting like `var` in javascript) – mpapec Apr 02 '18 at 15:20
  • See also [Nested subroutines and Scoping in Perl](https://stackoverflow.com/q/10192228/2173773) – Håkon Hægland Apr 02 '18 at 15:38
  • Related [post](https://stackoverflow.com/q/45670363/4653379) on nesting subroutines – zdim Apr 02 '18 at 17:22
  • 1
    I'm not sure where you're learning Perl from - but using `&` on subroutine calls is a bit of a clue that you're using a really old tutorial. That hasn't been necessary for over twenty years. – Dave Cross Apr 03 '18 at 11:24

2 Answers2

5

Others have already pointed out the issue with nested subs, but here is my answer to my understanding of your question:

The "scope" of a my declaration begins after the statement that contains the my, not at the beginning of the block it occurs in. Otherwise, both of these would work:

$ perl -wMstrict -le 'sub foo { my $bar="quz"; print $bar; } foo()'
quz
$ perl -wMstrict -le 'sub foo { print $bar; my $bar="quz"; } foo()'
Global symbol "$bar" requires explicit package name (did you forget to declare "my $bar"?) at -e line 1.
Execution of -e aborted due to compilation errors.

Also, note the following:

my $x = 3;
{
    my $x = $x + 5;
    print "1: $x\n";  # prints "1: 8"
}
print "2: $x\n";      # prints "2: 3"

In your first piece of code, at the point you declare sub b, Perl only knows about the global $x = 8, so that's what print "B: $x\n" is using. In the second piece of code, Perl has seen my $x = 3, so that's what it uses. This effect also occurs if you don't use nested subs:

my $x = 8;
sub a {
    my $subb = sub {
        print "B: $x\n";
    };
    my $x = 3;
    $subb->();
}
a();  # prints "B: 8"

and

my $x = 8;
sub a {
    my $x = 3;
    my $subb = sub {
        print "B: $x\n";
    };
    $subb->();
}
a();  # prints "B: 3"
haukex
  • 2,973
  • 9
  • 21
3

This problem arises from having nested named subs.

There's no reason to have nested name subs in Perl since the inner sub is in no way scoped to the outer sub.

Instead, use:

sub p {
    my ($x) = @_;

    local *q = sub {
        say $x;
    };

    q();
}

p($_) for 3, 4;

or

sub p {
    my ($x) = @_;

    my $q = sub {
        say $x;
    };

    $q->();
}

p($_) for 3, 4;
ikegami
  • 367,544
  • 15
  • 269
  • 518