Roger has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks, when I was playing with Data::Dumper this afternoon, I encountered yet another strange behaviour on what I think is lexical scoping of my variables. Consider the following code:

use strict; my $k = 1; print "wanted: \$k = $k, got " . InvestigateValue() . "\n"; my $k = 2; print "wanted: \$k = $k, got " . InvestigateValue() . "\n"; sub InvestigateValue { return defined $k ? $k : 'undef'; }
I expected the following result:
wanted: $k = 1, got 1 wanted: $k = 2, got 2
But no, that was not the case. I got this instead:
wanted: $k = 1, got undef wanted: $k = 2, got 2
Consider that my $k = 1; has two components, compile-time component my $k; and run-time component $k = 1;.

So when I get to the first print InvestigateValue(), the compile-time and run-time components of my $k = 1; have been evaluated, thus $k should be 1 here.

But from the observation, the compile-time component of the second my $k = 2; seems to propagate into run-time, and set the value of $k to undef, even-though the run-time component of my $k = 1 happens after the compile-time component of my $k = 2.

There is some strange thing going on here inside Perl, but I can't explain with the compile-time/run-time theory. The only explanation I can think of is that the Perl compiler created two 'entries' on the scratch pad for the same $k variable (one entry each time a my keyword is encountered, even though the variables are in the same scope with the same name), and the subroutine is refering to the second entry only.

Can other monks fill me in on this please?

Many thanks. -- Roger.

Replies are listed 'Best First'.
Re: Another variable scoping oddity
by Aristotle (Chancellor) on Nov 12, 2003 at 06:32 UTC

    You have created a closure.

    At the time the function is parsed and compiled, the second my's compile time effects have taken place; the closure is therefor bound to the second lexical called $k. Regardless where and when you call the function, the $k inside the function always refers to the second $k.

    At runtime, at the time of the first call to the function, no value has yet been assigned to that one. Meanwhile, the associated print refers to the first $k.

    Makeshifts last the longest.

      Thanks Aristotle for the excellent explanation. I understand a bit more on closure now.

Re: Another variable scoping oddity
by pg (Canon) on Nov 12, 2003 at 06:24 UTC

    Roger, if you run your code with -w, it gives you warning:

    "my" variable $k masks earlier declaration in same scope at a.pl line +6.

    Once you resolve the warning by only declaring $k once, everything is now fine:

    use strict; my $k = 1; print "wanted: \$k = $k, got " . InvestigateValue() . "\n"; $k = 2;#modified print "wanted: \$k = $k, got " . InvestigateValue() . "\n"; sub InvestigateValue { return defined $k ? $k : 'undef'; }
      I see, so that's the reason. That should teach me not to omit the -w flag, and add #!/usr/bin/perl -w to my code. I became too lazy these days. :(

Re: Another variable scoping oddity
by artist (Parson) on Nov 12, 2003 at 06:23 UTC
    use dignostics;
    "my" variable $k masks earlier declaration in same scope at program.pl line 7 (#1)
    (W misc) A "my" or "our" variable has been redeclared in the current scope or statement, effectively eliminating all access to the previous instance. This is almost always a typographical error. Note that the earlier variable will still exist until the end of the scope or until all closure referents to it are destroyed.

    artist

Re: Another variable scoping oddity
by davido (Cardinal) on Nov 12, 2003 at 06:32 UTC
    You may or may not have noticed, but if you use -w or "use warnings;", you get the following warning:

    "my" variable $k masks earlier declaration in same scope at mytest.pl +line 9.

    If you put your subroutine declaration and definition BEFORE the declaration of the first "my $k" you'll get the following error (under strictures):

    Global symbol "$k" requires explicit package name at mytest.pl line 8. Global Symbol "$k" requires explicit package name at mytest.pl line 8. Execution of mytest.pl aborted due to compilation errors.

    That is because $k is seen in the definition of the subroutine before it is declared by the first my $k; in the script.

    So your code as it stands now is passing $k into the subroutine 'globally' (In this case I use that term to mean, "not through the parameter list"). And the first declaration of $k is masked by the second. And since the sub is being declared and defined chronologically after the second my, the declaration of the first my $k is masked and not seen by the sub.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein