Hi

I didn't expect that it's possible to dynamically access a lexical var by using eval within a closure-function (here "getset()"), but indeed it's possible.

There is no real need for this in Perl¹ ... but I wanted to share the insight I got! :)

# Eval in Closure # Accessing lexical by name? # use strict; sub alert { print @_,"\n"; } ; my $getset; my $change =sub { my $it=shift; $getset->($it,"post"); }; my $x="global"; my $test=sub { my $x="pre"; $getset=sub { my ($name,$val)=@_; eval('$'.$name."='".$val."'"); # I can access any variable in this + scope }; alert($x); # pre $change->("x"); alert($x); # post } ; $test->(); alert($x); # global

The motivation for this code is that in JS one can't pass simple data-types (i.e. "scalars") by reference one need to use objects instead.

But when converting Perl-code to JS one can't control potential aliasing/referencing of parameters, e.g. a called sub could assigning to $_[].

This functional pattern could be used as workaround, and works in both languages.

Cheers Rolf

¹) Hmm ...maybe as generic accessor method in OOP...

Replies are listed 'Best First'.
Re: Accessing lexicals in other scopes dynamically by name
by ikegami (Patriarch) on Jul 30, 2010 at 15:48 UTC

    I didn't expect that it's possible to dynamically access a lexical var by using eval

    It would be a bit silly if one couldn't do

    my $greet = "Hello World"; eval ' print "$greet\n"; ';

    eval doesn't know how its argument was generated, so

    eval 'print "$gr' . 'eet\n";';
    and
    my $name = $greet; eval 'print "$' . $name . '\n";';
    are both equivalent to the first.

    Note: Subs only capture variables it knows it will need.

    $ perl -E' use warnings; use strict; sub f { my $n="ok"; sub { eval q{$n} } } say f->() ' Variable "$n" is not available at (eval 1) line 2. Use of uninitialized value in say at -e line 4. $ perl -E' use warnings; use strict; sub f { my $n="ok"; sub { $n; eval q{$n} } } say f->() ' Useless use of private variable in void context at -e line 3. ok $ perl -E' use warnings; use strict; sub f { my $n="ok"; sub { $n if 0; eval q{$n} } } say f->() ' ok
      > It would be a bit silly if one couldn't do

      Sure ... but...

      > Note: Subs only capture variables it knows it will need.

      which was exactly my intuition, you understand my confusion, since

      my enclosed sub from the OP:

      $getset=sub { my ($name,$val)=@_; eval('$'.$name."='".$val."'"); # I can access any variable in this + scope };

      doesn't know it will need to capture $x!

      So whats happening here?

      Cheers Rolf

        You never captured $x. It was simply still in scope. You basically did
        my $x="global"; my $test=sub { my $x="pre"; alert($x); # pre my ($name,$val)=@_; eval('$'.$name."='".$val."'"); alert($x); # post }
        which we've already established should work. Call the sub from outside the scope in which $x resides and you'll see it fail.
        Some changes to your code to make it clearer!

        use strict; use warnings; my $n="outer"; my $f; { my $n="ok"; $f=sub { eval "".shift }; check(); # "ok" }; sub check { print $f->('$n') } print $n; # "outer" exit;

        Cheers Rolf

      Note: Subs only capture variables it knows it will need.
      I don't think that is true.

      My understanding is that closures capture the whole environment (in the sense of lexical variable bindings).

      Otherwise this would not be possible:

      use strict; my $a = 1; my $b = 2; my $c = 3; sub f { my $var = shift; print $var, "=", eval "\$$var", "\n"; }; &f("a"); &f("b"); &f("c"); # prints a=1 b=2 c=3

        Nothing's captured. $a, $b, $c are still in scope, and we've already established that eval should and can access lexicals in scope.

        Place all by the calls to f() in curlies or in a module and you'll see it doesn't capture.

        use strict; { my $a = 1; my $b = 2; my $c = 3; sub f { my $var = shift; print $var, "=", eval "\$$var", "\n"; }; } &f("a"); &f("b"); &f("c");
        $ perl -w a.pl Variable "$a" is not available at (eval 1) line 2. Use of uninitialized value in print at a.pl line 11. a= Variable "$b" is not available at (eval 2) line 2. Use of uninitialized value in print at a.pl line 11. b= Variable "$c" is not available at (eval 3) line 2. Use of uninitialized value in print at a.pl line 11. c=
      > Note: Subs only capture variables it knows it will need.

      I ran some tests, IMHO thats not right, the point is rather that at run time the variable doesn't exist any more in your example.

      Since it's only referenced by symbol, there is no reference count on the variable and the garbage collector already deleted it.

      Cheers Rolf

        the point is rather that at run time the variable doesn't exist any more in your example.

        Exactly. And the reason it doesn't exist is because it wasn't captured. That would have kept it alive. You can see the capture increasing the ref count in the following snippet:

        $ perl -MDevel::Peek -e' sub f { my $x; Dump($x); my @a = map { sub { $x } } 1..3; Dump($x); } f() ' SV = NULL(0x0) at 0x817bc90 REFCNT = 1 FLAGS = (PADMY) SV = NULL(0x0) at 0x817bc90 REFCNT = 4 FLAGS = (PADMY)
        I think ikegami is right after all.

        Consider:

        use strict; { my $a = \$a; print "a=$a\n"; sub f { my $var = shift; print $var, "=", eval "\$$var", "\n"; }; } &f("a");

        $a references itself, so it will never be garbage-collected, yet it is not accessible in the eval.