6

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)

Community
  • 1
  • 1
Eric Strom
  • 39,821
  • 2
  • 80
  • 152
  • I'll be honest that I'm a little lost here. but could you do our $_; ? so you have access to $_ locally but also access to... $Package::$_ seriously though this is above my brain scope – xenoterracide Jul 28 '10 at 21:31
  • @xenoterracide => I am not sure where you are going with the package scope idea, but `$_` (and most of the other punctuation variables) are common across all packages and can not be package scoped with `our`. the lexical `$_` (`my $_;`) is a different beast altogether, but that's beyond the scope of this question, pardon the pun – Eric Strom Jul 28 '10 at 21:39
  • not sure I was going anywhere with it. but perl doesn't yell about `our $_;` so I'm assuming that $_ can be affected with that as well; maybe assuming wrong though. If I had a real answer I would have answered that way. – xenoterracide Jul 28 '10 at 22:05
  • What is the purpose of this? Why do you want to localize `$_` in the first place, and what would this gain? – Ether Jul 28 '10 at 22:53
  • @Ether => in my case, purely for stylistic reasons (eliminating boiler plate code), which is why I am giving weight to the performance impacts of CORE::GLOBAL modifications, since stylistic tweaks shouldn't cause far reaching performance issues. but i can imagine other uses for an `uplevel_local` or `goto_with_local` construct. – Eric Strom Jul 28 '10 at 23:44
  • Could you set a label instead of the named function? `output: { output(); sub output{print "outputting\n";} }`? – vol7ron Jul 29 '10 at 02:09
  • If you still want it to be dynamic, use the FORTRAN-like splice method `goto(("FOO", "BAR", "GLARCH")[$i]);` combine that with a hash or eval and you have your dynamic method. – vol7ron Jul 29 '10 at 02:24
  • May I ask why you need to preserve call frame depth? Generally code I write that inspects its stack location needs to take into account that it might be "mediated" by eval blocks, AUTOLOAD, sub wrappers, etc. – pilcrow Jul 31 '10 at 16:11

2 Answers2

1

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.

Michael Carman
  • 30,628
  • 10
  • 74
  • 122
  • `Sub::Uplevel` will do the job of tricking `caller` but I am not crazy about the way it goes about it (wrapping and localizing `CORE::GLOBAL::caller`). See the BUGS/CAVEATS section of `Sub::Uplevel` for more details. – Eric Strom Jul 28 '10 at 22:06
  • Ah, I (mis)read your question as "Sub::Uplevel didn't work" rather than as that you'd disqualified it due to how it worked. – Michael Carman Jul 29 '10 at 12:30
1

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?

Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
  • my concerns about performance are more for the code that I am not writing (users of the module). I can't say if they will be using `caller` or code that uses `caller` extensively, where the performance impact of going from a C builtin to a userland sub might be significant. Since the ultimate goal is just the syntactic nicety of having `$_` set for you, its hard to justify changes to the `CORE::GLOBAL` namespace which might cause performance regressions. – Eric Strom Jul 28 '10 at 22:16
  • I guess my ultimate issue is that I disagree with the design decision that locals should be unrolled before the `goto`, because it seems to unnecessarily reduce the power of the `goto-&NAME` construct. I would prefer if `goto-&NAME` adopted the local pad from the call site, and then rolled back the locals at the end of `&NAME`'s execution. This would probably also be faster than popping and pushing the scope stack each time. – Eric Strom Jul 28 '10 at 22:22