Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to localize a variable in an upper scope in Perl?

I have run across the following pattern a few times while developing Perl modules that use AUTOLOAD or other subroutine dispatch techniques:

sub AUTOLOAD {
    my $self = $_[0];

    my $code = $self->figure_out_code_ref( $AUTOLOAD );

    goto &$code;
}

This works fine, and caller sees the correct scope.

Now what I would like to do is to locally set $_ equal to $self during the execution of &$code. Which would be something like this:

sub AUTOLOAD {
    my $self = $_[0];

    my $code = $self->figure_out_code_ref( $AUTOLOAD );

    local *_ = \$self;

    # and now the question is how to call &$code

    # goto &$code;  # wont work since local scope changes will 
                    # be unrolled before the goto

    # &$code;  # will preserve the local, but caller will report an
               # additional stack frame  
}

Solutions that involve wrapping caller are not acceptable due to performance and dependency issues. So that seems to rule out the second option.

Moving back to the first, the only way to prevent the new value of $_ from going out of scope during the goto would be to either not localize the change (not a viable option) or to implement some sort of uplevel_local or goto_with_local.

I have played around with all sorts of permutations involving PadWalker, Sub::Uplevel, Scope::Upper, B::Hooks::EndOfScope and others, but have not been able to come up with a robust solution that cleans up $_ at the right time, and does not wrap caller.

Has anyone found a pattern that works in this case?

(the SO question: How can I localize Perl variables in a different stack frame? is related, but preserving caller was not a requirement, and ultimately the answer there was to use a different approach, so that solution is not helpful in this case)

like image 854
Eric Strom Avatar asked Jul 28 '10 21:07

Eric Strom


2 Answers

Sub::Uplevel appears to work -- at least for a simple case not involving AUTOLOAD:

use strict;
use warnings;
use Sub::Uplevel;

$_ = 1;
bar();

sub foo {
    printf  "%s %s %d - %s\n", caller, $_
}

sub bar {
    my $code = \&foo;
    my $x    = 2;
    local *_ = \$x;
    uplevel 1, $code;
}

The output is:

main c:\temp\foo.pl 6 - 2

Granted, this doesn't really localize a variable in the parent scope, but I don't think you would really want to do that even if you could. You only want to localize $_ for the duration of the call.

like image 80
Michael Carman Avatar answered Oct 17 '22 00:10

Michael Carman


The perlfunc documentation for goto points out (emphasis added)

The goto-&NAME form is quite different from the other forms of "goto". In fact, it isn't a goto in the normal sense at all, and doesn't have the stigma associated with other gotos. Instead, it exits the current subroutine (losing any changes set by local)

What sorts of performance concerns allow for indirection through autoloading but not through a wrapper?

like image 26
Greg Bacon Avatar answered Oct 17 '22 00:10

Greg Bacon