http://qs1969.pair.com?node_id=778392

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

#!/usr/local/bin/perl use strict; my $foo = 1; my $fum = 1; print STDERR "variables are now: $foo $fum\n"; &foo(); my $foo = 2; $fum = 2; print STDERR "variables are now: $foo $fum\n"; sub foo { print STDERR "variables are now: $foo $fum\n"; }
I expected this to print:
variables are now: 1 1 variables are now: 1 2 variables are now: 2 2
Could some kind Monk offer enlightenment, as to why my expectations were dashed? (Tested on perl 5.8.8 and Perl 5.10.0, with and without the in-this-case-helpful -w flag).

Replies are listed 'Best First'.
Re: What is it I don't understand about perl scoping?
by jettero (Monsignor) on Jul 08, 2009 at 20:06 UTC
    What you don't understand is that perl compiles that sub foo and compiles the lexical-ness of the my declarations before it executes the foo() and before it sets those lexicals below the function call.

    I think it's clearer like this. It's nearly the same thing, but it doesn't have the lexical masking that yours has. That just confuses the issue.

    use strict; use warnings; foo(); my ($foo, $berries) = (1,2); foo(); sub foo { print "$foo, $berries\n"; }

    Here you can clearly see I'm using the foo and berries declared lexically after the function is called. The declaration happens at compile time and the initialization happens at runtime.

    -Paul

      ++ excellent answer, thanks.
Re: What is it I don't understand about perl scoping?
by JavaFan (Canon) on Jul 08, 2009 at 20:03 UTC
    foo doesn't see the $foo you are assigning 1 to. It sees the $foo you are assigning 2 to, but at that time, it's still uninitialized.

    my always creates a new variable. The creation of the variable is done at compile time (and hence, at that time it's "decided" which variable a line of code "sees"); the initialization is done run time.

Re: What is it I don't understand about perl scoping?
by Utilitarian (Vicar) on Jul 09, 2009 at 08:31 UTC
    As you say the use warnings flag would have told you you were calling my on a variable already declared in the same scope. compare your code with that below
    #!/usr/local/bin/perl use strict; my $foo = 1; my $fum = 1; print STDERR "variables are now: $foo,$fum\n"; &foo(); { my $foo = 2; $fum = 2; print STDERR "variables are now: $foo,$fum\n"; } sub foo { print STDERR "variables are now: $foo,$fum\n"; } variables are now: 1,1 variables are now: 1,1 variables are now: 2,2
    By enclosing the redeclaration in a block you avoid scope confusion
Re: What is it I don't understand about perl scoping?
by YuckFoo (Abbot) on Jul 09, 2009 at 18:18 UTC
    While I appreciate you may be just using this code to understand how scoping works, the advice I think you really need is quit having these thoughts and expectations, resist. These variables should be passed to the function. Stop running with scissors!
    #!/usr/local/bin/perl use strict; my $foo = 1; my $fum = 1; print STDERR "variables are now: $foo $fum\n"; foo($foo, $fum); my $foo = 2; $fum = 2; print STDERR "variables are now: $foo $fum\n"; sub foo { my $foo = shift; my $fum = shift; print STDERR "variables are now: $foo $fum\n"; }
Re: What is it I don't understand about perl scoping?
by youlose (Scribe) on Jul 09, 2009 at 12:05 UTC
    use strict;
    
    my $foo = 1;
    my $fum = 1;
    
    print STDERR "variables are now: $foo $fum\n";
    
    {
        my $foo = 2;
        $fum = 2;
        &foo();
        print STDERR "variables are now: $foo $fum\n";
    
    }
    
    sub foo {
        print STDERR "variables are now: $foo $fum\n";
    
    }
    
    
    
    
    Resulting:
    variables are now: 1 1
    variables are now: 1 2
    variables are now: 2 2
    
    as you wanted
Re: What is it I don't understand about perl scoping?
by mattford63 (Sexton) on Jul 09, 2009 at 21:00 UTC
    This is as much for my own understanding and may not be entirely correct for Perl (I hope so though).

    The function &foo has "free variables" (e.g., variables used within the function body that are NOT declared within the function body). This means that Perl has to go outside the scope of the function to try and provide values for them.

    The first place it tries is the scope at which the function was called. Update: that's actually not true. It first tries the scope above the function definition, and then the scope above that it until it finds a declaration or stops at the global scope. In the original code it just so happens the scope at which it finds the declarations is the same as where the function is called - see the supplemental example. Now, you might think that as you declare $foo in front of the function call it should have a value of 1 (it is in the same scope after all). However within the same scope of the first $foo declaration there is a second $foo declaration. The second declaration effectively "masks" the first. Perl doesn't like having two declarations in the same scope so chooses the last one it finds as the one to use. And as this happens to be after the function call, the values aren't initialised when &foo is called.

    I'm not sure what actually happens behind the scenes i.e. what does "mask" really mean. Does the second declaration actually overwrite the first? Or is the second declaration always found and used before the first?

    What's the truth monks?

    Supplemental Example

    #!/usr/bin/perl use strict; use warnings; my ($foo, $berries) = (89,90); foo(); { my $foo = 89; sub foo { print "$foo, $berries\n"; }; };

    Results in:

    ,90

    When sub foo is compiled it can't find a declaration for $foo so checks the parent scope. It finds it at the line "my $foo = 89". At this point Perl binds the varible $foo to a memory location but doesn't set it's value i.e., it just does the declaration not the initialisation. Further this binding is stored with the function sub $foo (note: this is a closure - the free variable $foo in sub foo gets bound (or closed) in the parent scope and permanently attached to the function).

    So, even though in the global context $foo is set before the sub foo call, it doesn't matter because sub foo has it's own binding for $foo which hasn't yet been initialised.

Re: What is it I don't understand about perl scoping?
by kubrat (Scribe) on Jul 13, 2009 at 16:47 UTC

    I understand what and why Perl does what it does in this case but isn't it more natural to expect this:

    variables are now: 1 1 variables are now: 1 1 variables are now: 2 2

    Instead of:

    variables are now: 1 1 variables are now: 1 2 variables are now: 2 2
    ???