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

Hi all

Suppose you have the following dummy example of a closure: (The real example I'm working on do a little more interesting thing.)

my $print = emphasizePrint(); $print->("Hello World"); ## Prints "Hello World!!" sub emphasizePrint { my $k = "!!\n"; return sub { my ($str) = @_; eval (q{print "$str$k"}); print STDERR "Error: $@" if ($@); } }

The problem is that this example doesn't work because the eval statement is compiled at run-time where it is called, that is outside of the emphasizePrint subroutine and there $k is out of scope, right?

The solution would be to include $k in the returned subroutine but outside the eval statement, avoiding $k to be "garbage-collected". The subroutine emphasizePrint would look like this:

sub emphasizePrint { my $k = "!!\n"; return sub { $k; ## <- INSERTED my ($str) = @_; eval (q{print "$str$k"}); print STDERR "Error: $@" if ($@); } }

But I'm not fully comfortable with this solution. Is this correct? Is there another (better) way of coping with this?

Thank you very much in advance

citromatik

Replies are listed 'Best First'.
Re: Scope problem in closures with an eval statement
by moritz (Cardinal) on Apr 14, 2008 at 11:02 UTC
    The problem is not that $k is garbage collected, but that it's not included in the closure. Actually your first sub doesn't return a closure at all, because no variables from the outer scope are enclosed (at compile time) in the anonymous sub at all.

    If you can in your case, you could use the non-string form of eval:

    sub emphasizePrint { my $k = "!!\n"; return sub { my ($str) = @_; eval { print "$str$k" }; print STDERR "Error: $@" if ($@); } }

    If that isn't applicable in your case I know no other way around than mentioning the variables in the closure once.

      No, the problem is that $k is not available (slightly different than garbage collected). It is included in the closure, as this demonstrates:
      use strict; use warnings; $::clos = $::clos = "global"; sub foo { my $clos = shift; my $sub = shift; &$sub() if $sub; return sub { eval 'print "$clos\n"' } } foo("baz", foo("bar"));
      The first (inside) call to foo returns a closure on foo's $clos. The second call to foo calls the closure, which does get the closed-over $clos. But there is a problem: entering foo the second time reuses the same lexical $clos, since perl isn't aware that the first call will be referring to it, so as an optimization doesn't bother allocating a new lexical.

      Note that in 5.10.0, the OP's problem code gives a warning:

      Variable "$k" is not available at (eval 1) line 1.
Re: Scope problem in closures with an eval statement
by Fiftyvolts (Novice) on Apr 14, 2008 at 13:23 UTC

    moritz has pretty much answered the technical aspect of your question, but now I am curious about your design choice. What are you trying to accomplish with this closure you are creating? There is a good chance that there is a different way of doing what you are looking for that is less complicated

    My instinct is that you are trying to perform some sort of wrapping functionality with out increasing the stack frame you are working with. In otherwords, do something, then call your function, but keep @_ exactly as was originally called. Below is an example.

    use strict; sub trace_wrapper_no_frame { my $f=shift; return sub { print "Trace, no frame\n"; goto $f; }; } sub trace_wrapper_frame { my $f=shift; return sub { print "Trace, with frame\n"; $f->(@_); # <----- LINE 15 }; } sub who_says_hello { print(join(" ",caller), "\n"); print "Hello, $_[0]\n"; } who_says_hello("World"); # <------ LINE 26 print '-' x 20, "\n"; my $frame_wrapped_hello = trace_wrapper_frame(\&who_says_hello); $frame_wrapped_hello->("dolly"); print '-' x 20, "\n"; my $no_frame_wrapped_hello = trace_wrapper_no_frame(\&who_says_hello); + $no_frame_wrapped_hello->("Fifty"); #<----- LINE 36

    The output looks like the following

    From line # 26 Hello, World -------------------- Trace, with frame From line # 15 Hello, dolly -------------------- Trace, no frame From line # 36 Hello, Fifty

    But don't do that if you don't have to. Hook::LexWrap is a great module that does it all for you, but with more features allowing you to specify pre and post call wrappers, and allows you to lexically scope said wrapping if you so desire.

    .... Then again, this might have nothing to do with what you want...

Re: Scope problem in closures with an eval statement
by ysth (Canon) on Apr 15, 2008 at 07:01 UTC
    If you actually need the string eval, you've found the right solution. You can get rid of the "Useless use of private variable in void context" warning (you do have warnings enabled, right?) by saying "$k if 0;" instead. And do comment it so someone doesn't wander along and delete the "useless" line.
Re: Scope problem in closures with an eval statement
by dwindura (Novice) on Apr 15, 2008 at 08:42 UTC
    I believe the problem is that you use q (single quote) around the print "$str$k, thus the variables were not interpolated.

    You should use qq (double quote) instead.
    my $print = emphasizePrint(); $print->("Hello World"); ## Prints "Hello World!!" sub emphasizePrint { my $k = "!!\n"; return sub { my ($str) = @_; eval (qq{print "$str$k"}); print STDERR "Error: $@" if ($@); } }
      That's really a bad idea in the general case, because it won't work if $k has a string terminator in it:
      sub emphasizePrint { my $k = qq{"!!\n}; return sub { my ($str) = @_; eval (qq{print "$str$k"}); print STDERR "Error: $@" if ($@); } } __END__ Error: Can't find string terminator '"' anywhere before EOF at (eval 1 +) line 2.

      In the general case this opens lots of holes, which can be a security risk.

      I think that citromatik deliberately interpolated the variables at eval time, not prior to evaling the string.