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

testcall1.pl: #!/usr/bin/perl use strict; require "testcall2.pl"; func1 (); testcall2.pl: #!/usr/bin/perl use strict; my ($sep, $z); sub func2 { print "func2: \$sep == $sep\n"; eval '$z = sub { print "sep == ($sep)\n"; }'; &{$z}(); } sub func1 { $sep = "AB"; func2(); }
Calling 'perl testcall1.pl' produces output
--
func2: $sep == AB
sep == (AB)
--

Comment out the print "func2... line, then run again. Now it produces output
--
sep == ()
--

So why does that print statement change the value of $sep at a different point?

Replies are listed 'Best First'.
Re: A 'print' at one spot changes a value at another
by lidden (Curate) on Aug 15, 2005 at 05:08 UTC
    With warnings and use diagnostics I get this explanation:
    Variable "$sep" is not available at (eval 9) line 1 (#1)
        (W closure) During compilation, an inner named subroutine or eval is
        attempting to capture an outer lexical that is not currently available.
        This can happen for one of two reasons. First, the outer lexical may be
        declared in an outer anonymous subroutine that has not yet been created.
        (Remember that named subs are created at compile time, while anonymous
        subs are created at run-time.) For example,
        
            sub { my $a; sub f { $a } }
        
        At the time that f is created, it can't capture the current value of $a,
        since the anonymous subroutine hasn't been created yet. Conversely,
        the following won't give a warning since the anonymous subroutine has by
        now been created and is live:
        
            sub { my $a; eval 'sub f { $a }' }->();
        
        The second situation is caused by an eval accessing a variable that has
        gone out of scope, for example,
        
            sub f {
            my $a;
            sub { eval '$a' }
            }
            f()->();
        
        Here, when the '$a' in the eval is being compiled, f() is not currently being
        executed, so its $a is not available for capture.
        
    Use of uninitialized value $sep in concatenation (.) or string at (eval 9) line
            1 (#2)
        (W uninitialized) An undefined value was used as if it were already
    ... snip ...
    

      I can't reproduce the "closure" warning; only the "uninitialized" one. I tried it on 5.8.3 and 5.8.6. Can you give more details (e.g. perl version, any changes to the code, etc.)? Thanks!

      the lowliest monk

        That warning only appears in bleedperl, ie 5.9.x

        update: To be precise, as of change #19637, "jumbo closure fix"

        Dave.

Re: A 'print' at one spot changes a value at another
by chromatic (Archbishop) on Aug 15, 2005 at 04:12 UTC

    At compile time, perl doesn't know that the eval string will close over $sep, so it doesn't. If you changed it to a normal closure, it ought to work.

    Arguably this is or is not a bug. (If you think it is a bug, please post an algorithm that reliably fixes the bug. Good luck.)

      A even better advise is to avoid closure entirely.

      In my actual program, the code to the function is generated at runtime, so I can't use a normal closure. Changing the respective 'my' to 'our' seems to have fixed the code, but I'm really bothered by the inconsistency that it works when I have that print statement.

      (Maybe the print statement prevents it from making some optimization which isn't valid in the situation?)

        The print statment lets perl know to close on the $sep variable. Without the print, perl doesn't know you want to access $sep because it doesn't look inside the eval. Changing to our makes $sep global so that it will always be available, not just when perl sees a function is going to use it.


        ___________
        Eric Hodges
Re: A 'print' at one spot changes a value at another
by Tanktalus (Canon) on Aug 15, 2005 at 04:12 UTC

    It looks like a closure to me. Based on some really wierd combination of the require and the my variable. Since perl doesn't see that func2 uses the "global" lexical $sep, the variable is not bound to func2. So you end up with a different $sep from the lexical that you think you're seeing.

    That said, I get funny results when I change testcall2.pl:

    #!/usr/bin/perl use strict; use warnings; my ($sep, $z); sub func2 { #print "func2: \$sep == $sep\n"; eval '$z = sub { print "sep == ($sep)\n"; printf "sep => %p\n", \$ +sep}'; &{$z}(); } sub func1 { $sep = "AB"; printf "sep => %p\n", \$sep; func2(); } 1;
    This shows the address to be the same - although I'm not sure I'm doing that right. However, I think my theory is right based on the fact that if I change "my" to "our", so we're using global variables and not lexicals (thus no more closure), then things work the way we think it should.