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

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

Ok, I have a package named Bob that looks like this:
package Bob; my $thingy = {One => 1, Two => 2}; sub doit { my $key = '{One}'; eval "print \"thingy one is \$thingy->$key\n\""; #my $x = $thingy; } 1;
And I use it in a program like so:
#!/usr/local/bin/perl use strict; use warnings; use Bob; Bob->doit();
I get the following when I run the program:
Use of uninitialized value in concatenation (.) or string at (eval 1) +line 1. thingy one is
However, if I uncomment the "my $x" line in Bob.pm, it works fine:
thingy one is 1
I'm assuming this is a bug in perl. Is it fixed in 5.8.8?

I'm running perl 5.8.6 under Linux (FC4).

Thanks!

Replies are listed 'Best First'.
Re: "eval" and "my" variable weirdness
by japhy (Canon) on Jun 29, 2006 at 16:05 UTC
    I don't think it's a bug, but I might be wrong. The problem is that there's no reason for Perl to keep $thingy (a lexical) around once it's done compiling Bob.pm because nothing that "lasts" refers to it. However, if you have my $x = $thingy in a function in Bob.pm, that basically produces a closure around $thingy and keeps $thingy in existence.

    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart

      Absolutely right japhy, but some might find your explanation a bit hard to swallow. It's a complicated topic, so I'll try to illustrate it.


      The minimal code illustrating the problem is:

      # Module.pm use warnings; package Module; my $thingy = 'thangy'; sub doit { eval 'print "thingy is $thingy\n"'; } 1;
      use Module; Module::doit(); # "thingy is "

      Each Perl files (both scripts and modules) create a scope. Just like adding {...} around statements creates a scope for the contained statements, use, require, etc create a scope in which the file is executed.

      The means the above code is equivalent to:

      { use warnings; package Module; my $thingy = 'thangy'; sub doit { eval 'print "thingy is $thingy\n"'; } 1; } Module::doit(); # "thingy is "

      Let's simplify the above to:

      { use warnings; my $thingy = 'thangy'; sub doit { eval 'print "thingy is $thingy\n"'; } } doit(); # "thingy is "

      When Perl reached the "}", it doesn't realize you'll still need $thingy (since the eval hasn't run yet), so $thingy is freed. If you added use strict; to the string being evaled, you'd get a strict error. eval's $thingy refers not to the lexical, but to the package variable $main::thingy.

      Enter closures. Closures force variables to stick around longer than they normally would:

      { use warnings; my $thingy = 'thangy'; sub doit { print "thingy is $thingy\n"; } } doit(); # "thingy is thangy"

      So you need to add a reference to $thingy in your sub so Perl knows (at compile-time) that $thingy is still needed:

      { use warnings; my $thingy = 'thangy'; sub doit { my $ref = \$thingy; # Close over $thingy eval 'print "thingy is $$ref\n"'; } } doit(); # "thingy is thangy"

      By "reference", I meant "occurance". In the previous snippet, my reference to $thingy is in the expression creating a reference to $thingy, but it need not be so:

      { use warnings; my $thingy = 'thangy'; sub doit { my $thingy = $thingy; # Close over $thingy eval 'print "thingy is $thingy\n"'; } } doit(); # "thingy is thangy"

      Alternatively, use package variables instead of lexical variables. They don't care about scope:

      { use warnings; our $thingy = 'thangy'; sub doit { eval 'print "thingy is $thingy\n"'; } } doit(); # "thingy is thangy"

        You're working too hard and breaking stuff. Don't redeclare $thingy, just use it once. When you redeclare you've just gotten yourself a whole different thingy and not the thingy you meant to get. Same data tho. Just a different thingy.

        { use warnings; my $thingy = 'thangy'; sub doit { $thingy; # Close over $thingy eval 'print "thingy is $thingy\n"'; } }

        ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: "eval" and "my" variable weirdness
by dave_the_m (Monsignor) on Jun 29, 2006 at 17:00 UTC
    Note that from 5.9.0 onwards you get a run-time warning:
    $ perl590 -w /tmp/p Variable "$x" is not available at (eval 1) line 1. Use of uninitialized value in concatenation (.) or string at (eval 1) +line 1. x= $ cat /tmp/p #!/usr/bin/perl { my $x = 1; sub f { eval q(print "x=$x\n") } } f;

    Dave.

Re: "eval" and "my" variable weirdness
by tlm (Prior) on Jun 29, 2006 at 16:41 UTC

    Here's another example:

    # foo.pl use strict; use warnings; require 'bar.pl'; frobozz( 2 ); __END__ # bar.pl use strict; use warnings; { my $x; # same thing with "my $x = 'whatever';" frobozz( 1 ) unless caller; sub frobozz { ( $x ) = @_; print 'NOT ' unless defined eval( '$x' ); print "OK\n"; quux(); } sub quux { # $x = $x; print 'NOT ' unless defined eval( '$x' ); print "OK\n"; } } 1; __END__
    If one runs foo.pl, the output is
    OK
    NOT OK
    
    ...meaning that frobozz sees $x but quux doesn't. If one uncomments the commented line in quux or runs bar.pl directly, the output is
    OK
    OK
    

    (This is true for both 5.8.6 and 5.8.8 on Linux.)

    After re-reading the docs on eval, I can't see how a programmer can be expected to predict this behavior. Therefore, it is, at the very least, a design bug, IMO.

    the lowliest monk

      Again, the "problem" is that, when bar.pl is require()d, Perl has to make some scope decisions. The quux() function doesn't make any claims to $x, so it's not a closure around $x. frobozz() is a closure around $x.

      Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
      How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
Re: "eval" and "my" variable weirdness
by CountZero (Bishop) on Jun 29, 2006 at 16:01 UTC
    Forget this. I have no idea why adding a statement AFTER the eval would suddenly make it work.In my Windows ActiveState Perl 5.8.7 it works without errors, ... sort of.

    The result is "thingy one is" because you have included curved brackets around the key value!. So if I change my $key = '{One}'; to my $key = 'One';, the result isbecomes "thingy one is 1->One". So clearly your eval is not doing what you think it is doing.

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

      The curlies are intentional. In my actual app, $thingy points to a complex data structure, and $key could be something like {One}[5]{France}. That's why I'm using eval with a string instead of a block.