In Perl, what is the most reliable way to determine a coderef's package?

Viewed 534

I have a number of higher order utility functions that take in a code reference and apply that code to some data. Some of these functions require localizing variables during the execution of the subroutines. At the beginning, I was using caller to determine which package to localize into, in a similar manner as shown in this example reduce function:

sub reduce (&@) {
    my $code      = shift;
    my $caller    = caller;
    my ($ca, $cb) = do {
        no strict 'refs';
        map \*{$caller.'::'.$_} => qw(a b)
    };
    local (*a, *b) = local (*$ca, *$cb);
    $a = shift;
    while (@_) {
        $b = shift;
        $a = $code->()
    }
    $a
}

Initially this technique worked fine, however as soon as I tried writing a wrapper function around the higher order function, figuring out the correct caller becomes complicated.

sub reduce_ref (&$) {&reduce($_[0], @{$_[1]})}

Now in order for reduce to work, I would need something like:

    my ($ca, $cb) = do {
        my $caller = 0;
        $caller++ while caller($caller) =~ /^This::Package/;
        no strict 'refs';
        map \*{caller($caller).'::'.$_} => qw(a b)
    };

At this point it became a question of which packages to skip, combined with the discipline of never using the function from within those packages. There had to be a better way.

It turns out that the subroutine the higher order functions take as an argument contains enough meta-data to solve the problem. My current solution is using the B introspection module to determine the compiling stash of the passed in subroutine. That way, no-matter what happens between compilation of the code and its execution, the higher order function always knows the correct package to localize into.

    my ($ca, $cb) = do {
        require B;
        my $caller = B::svref_2object($code)->STASH->NAME;
        no strict 'refs';
        map \*{$caller.'::'.$_} => qw(a b)
    };

So my ultimate question is if this is the best way of determining the caller's package in this situation? Is there some other way that I have not thought of? Is there some bug waiting to happen with my current solution?

2 Answers
Related