rbennett485 rbennett485 - 4 years ago 108
Perl Question

Perl scoping - accessing variables in subroutine

I am doing some code golf, and decided to try and be 'smart' and declare a subroutine where it would have the variables it needs already in scope, to avoid the extra code of having to pass in the arguments:

#! perl

use strict;
use warnings;


for my $i(0..1) {
my @aTest = (1);

sub foo {
# first time round - @aTest is (1)
# second time round - @aTest is (1,2)

push @aTest, 2;

# first time round - @aTest is (1,2)
# second time round - @aTest is (1,2,2)

my $unused = 0;
}

foo();
}


foo
sees the variable
@aTest
, and it has the expected value of
(1)
the first time I enter
foo
, before it pushes
2
onto the array as well.
@aTest
now looks like
(1,2)
.

So far so good.

Then we exit
foo
, and commence the second round of the for loop.
@aTest
is reassigned to
(1)
again.

We enter
foo
for the second time, but
@aTest
retains the value it previously had in
foo
(i.e.
(1,2)
), and then pushes another
2
on to become
(1,2,2)
.

What's going on here?

I assumed that since
@aTest
was in the same scope, it would be referring to the same variable both insed and outside
foo
. So how is it that inside `foo it retains its old value?

Answer Source

I posted this question into #p5p on irc.perl.org and got an interesting exchange that explains what's going on.

[15:13:35] <simbabque> can someone explain what's going on in Perl scoping - accessing variables in subroutine ? I've tried to read the output of B::Concise for that program, but my understanding is not strong enough. Could the behaviour we see there be a bug?
[15:15:35] <rjbs> &foo is a closure over @aTest.
[15:16:33] <haarg> but only the first @aTest, because &foo is created once at compile time
[15:17:01] <rjbs> Right.
[15:18:09] <rjbs> declaring a named sub inside of anything other than a package or bare block is, in my experience, asking for future heartache
[15:18:23] <alh> If you had my $foo = sub { }; $foo->(); it would work as expected
[15:18:34] <alh> Or in newer perls, with use feature qw(lexical_subs); my sub foo { } foo() would also work
[15:18:35] <simbabque> well the guy said he came across it while golfing
[15:19:13] <simbabque> alh: with both of those I had also expected it to be a closure, but because the sub foo {} gets done at compile time I was confused [15:19:38] <rjbs> lexical subs "do the right thing" with regard to binding [15:19:45] <alh> Still a closure, just re-evaluted every time [15:20:56] <haarg> they share the op tree, but are bound to different variables
[15:23:29] <simbabque> I added say "foo: ".\@aTest; and say "out ".\@aTest; inside and outside the function. That's also weird. the first round both are the same, then foo keeps the same one, and the one in the loop gets a new address and keeps it in subsequent iterations
[15:26:48] <alh> Sure, the first run through the loop, the variable that's closed over in the sub is the same one the loop sees
[15:27:01] <alh> Then we loop, and get a brand new one, but the sub doesn't (because it's not compiled again)
[15:27:46] <simbabque> alh: that makes sense, but why do all subsequent iterations reuse the same variable but reset it in the loop? Is that just Perl being smart with its memory?
[15:28:48] <alh> No, your sub has closed over a variable and maintains a reference to it - so it never goes away and its value is maintained across sub calls
[15:29:07] <alh> There's no "my @aTest" in the sub to "reset" the variable
[15:29:19] <alh> So it just keeps its value - that's the point of a closure
[15:29:21] <simbabque> alh: I meant the one in the loop, not in the sub
[15:29:41] <alh> What's happening to the one in the loop that is surprising you?
[15:29:52] <simbabque> out: ARRAY(0x2356300) foo: ARRAY(0x2356300) out: ARRAY(0x20978b0) foo: ARRAY(0x2356300) out: ARRAY(0x20978b0) foo: ARRAY(0x2356300) out: ARRAY(0x20978b0) foo: ARRAY(0x2356300)
[15:30:13] <simbabque> the first round they are the same
[15:30:33] <simbabque> then out (that's the my @aTest in the loop) gets a new address, but the round after that it stays the same
[15:30:49] <haarg> the address stays the same
[15:30:54] <haarg> that doesn't mean it's the same variable
[15:31:02] <haarg> more "gets the same address again"
[15:31:12] <simbabque> so Perl just reuses that address?

The output I am referring to is from this modification:

for my $i(0..3) {
  my @aTest = (1);

  sub foo {
    push @aTest, 2;
    my $unused = 0;
    print " foo: ".\@aTest;
  }
    print " out: ".\@aTest;

  foo();
}

So it's essentially building a closure over the @aTest at compile time. In the first iteration, the variable in the loop is the same as in the sub. In all subsequent iterations it creates a new variable in the loop, so we see a fresh (1) every time. But the sub does not get compiled again, so the @aTest variable in there stays the same and grows.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download