Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Scope surprise

by smcdonald (Initiate)
on Apr 10, 2009 at 23:41 UTC ( [id://756943]=perlquestion: print w/replies, xml ) Need Help??

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

At least it was a surprise to me. Here's a minimal version of the code to reproduce the issue:
use strict; my $foo = 1; foo(); my $foo = 2; sub foo { print "foo: $foo\n"; }
Of course, re-my'ing the same variable isn't ideal, but the behavior surprised me. I would expect it to output "foo: 1" but it actually outputs "foo: ". It turns out that $foo is undef within the foo() function. Changing the second my:
my $foo = 2;
to:
$foo = 2;
causes it to output "foo: 1" as expected. What's going on here? I see the same thing in perl 5.8.8 and 5.10.0.

Replies are listed 'Best First'.
Re: Scope surprise
by Nkuvu (Priest) on Apr 11, 2009 at 00:17 UTC

    If you had use warnings in the script you'd see something like "my" variable $foo masks earlier declaration in same scope at scriptname.pl line 6.

    I'm pretty sure that Perl is parsing the script prior to actually executing it, seeing the second declaration of $foo which masks the first declaration, and using that second variable in the same scope to actually execute the script. So you're calling the subroutine before defining the second $foo, meaning you print an uninitialized value.

    You would see the same output if you had a script like

    use strict; foo(); my $foo = 2; sub foo { print "foo: $foo\n"; }

Re: Scope surprise
by AnomalousMonk (Archbishop) on Apr 11, 2009 at 01:14 UTC
    Further to Nkuvu's reply above:

    To get a better idea of compile-time versus execution-time activity, enclose the second definition of the lexical variable in its own block, and make it a  BEGIN or  INIT block.

    Now, the compiler is happy with the second variable (because it lives in its own lexical scope), and whenever  foo() is called, the second variable always has a well-defined value.

    (Indeed, the second variable has a very well-defined value: because there is no 'mutator' function defined for it within its scope, it cannot possibly change during the execution of the script.)

    >perl -wMstrict -le "my $scalar = 99; foo(); my $scalar = 42; sub foo { print qq{scalar in foo: $scalar} } " "my" variable $scalar masks earlier declaration in same scope at ... Use of uninitialized value in concatenation (.) or string at ... scalar in foo: >perl -wMstrict -le "my $scalar = 99; foo(); BEGIN { my $scalar = 42; sub foo { print qq{scalar in foo: $scalar} } } " scalar in foo: 42
Re: Scope surprise
by shmem (Chancellor) on Apr 11, 2009 at 07:19 UTC
    What's going on here?

    The second my $foo masks the first; you know that. So the compiler allots different slots for them. Now, the sub foo happens to be in the same scope as that second my $foo, but at the time you call that function, it has not gotten a value assigned, so it is undef.

    use Devel::Peek; use strict; $|=1; my $foo = 1; print "1st foo: "; Dump($foo); foo(); my $foo = 2; print "2nd foo: "; Dump($foo); sub foo { print "sub foo: "; Dump($foo); print "foo: $foo\n"; } __END__ 1st foo: SV = IV(0x99b1c8c) at 0x9996768 REFCNT = 1 FLAGS = (PADBUSY,PADMY,IOK,pIOK) IV = 1 sub foo: SV = NULL(0x0) at 0x9996a8c REFCNT = 2 FLAGS = (PADBUSY,PADMY) foo: 2nd foo: SV = PVIV(0x9997b10) at 0x9996a8c REFCNT = 2 FLAGS = (PADBUSY,PADMY,IOK,pIOK) IV = 2 PV = 0

    The second my $foo is allocated at 0x9996a8c, and that's the one seen in the sub.

Re: Scope surprise
by ikegami (Patriarch) on Apr 11, 2009 at 07:30 UTC

    To put it more succinctly,

    There are three variables in scope with the same name (package var $foo aka $main::foo, the first lexical var $foo and the second lexical var $foo) so Perl needs to choose one to use. Perl always chooses the last one declared, falling back on the package var.

    By the way, you would have gotten a warning.

    Update: Fixed type in var name.

Re: Scope surprise
by bart (Canon) on Apr 11, 2009 at 18:11 UTC
    Did you know you could actually get the result you expected, just by replacing my with local? It changes the two lexicals into one global variable that temporarily changes value.
    local $foo = 1; foo(); local $foo = 2; sub foo { print "foo: $foo\n"; }
    Of course, I did have to drop the "use strict;" to make it work, because local is not a variable declaration. You can still prepend "our $foo;" to add the actual declaration, and strict will no longer complain.
      Did you just recommend
      our $foo; local $foo = 1; foo(); local $foo = 2; foo(); sub foo { print "foo: $foo\n"; }
      over
      my $foo; $foo = 1; foo(); $foo = 2; foo(); sub foo { print "foo: $foo\n"; }
      Either way, sounds like an awfully complicated way of avoiding passing a parameter.
      foo(1); foo(2); sub foo { my ($foo) = @_; print "foo: $foo\n"; }
        Did you just recommend ... over ...
        Yes I did. Not for this particular example, but examples (made up in just a few minutes) are always simpler than real life situations. Anyway, the basic idea here was that the OP could get exactly the result he was hoping for with almost the exact code he originally used, and understands so well. That is remarkable enough to warrant pointing it out.

        A more complex, and likely more true to life example could be this:

        $foo = bar(@params); # You don't know what value $foo has { local $foo = 1; foo(); } sub foo { print "foo: $foo\n"; } # now $foo is restored!
        You don't know what value $foo had, and if you want to restore the value it originally had, local comes in very handy.

        You may not even know if it matters whether $foo is restored, and you don't even have to know. You can just use local anyway, just to be on the safe side. That kind of localization of side effects is a very good tool to have in the toolbelt.

        Either way, sounds like an awfully complicated way of avoiding passing a parameter.
        Not a parameter. A setting. For example, suppose you temporarily want to change the settings for a module like Data::Dumper, and not disturb its value for everywhere else besides this piece of code:
        { local $Data::Dumper::Terse = 1; $baz = Dumper($data); }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://756943]
Approved by Bloodnok
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (5)
As of 2024-04-16 11:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found