2011-07-12 15 views
10

Tengo una serie de funciones de utilidad de orden superior que toman una referencia de código y aplican ese código a algunos datos. Algunas de estas funciones requieren la localización de variables durante la ejecución de las subrutinas. Al principio, yo estaba usando caller para determinar cuál es el paquete de localizar en, de una manera similar como se muestra en este ejemplo reduce función:En Perl, ¿cuál es la forma más confiable de determinar el paquete coderef?

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 
} 

Inicialmente esta técnica funcionó bien, sin embargo, tan pronto como he tratado de escribir un envoltorio funcionar alrededor de la función de orden superior, descubrir quién llama se vuelve complicado.

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

Ahora con el fin de reduce para trabajar, necesitaría algo así como:

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

En este punto se convirtió en una cuestión de qué paquetes para saltar, combinado con la disciplina de la que nunca se utiliza la función desde dentro de esos paquetes. Tenía que haber una mejor manera.

Resulta que la subrutina que las funciones de orden superior toman como argumento contiene suficientes metadatos para resolver el problema. Mi solución actual es usar el módulo de introspección B para determinar el alijo de compilación de la subrutina aprobada. De esta forma, pase lo que pase entre la compilación del código y su ejecución, la función de orden superior siempre conoce el paquete correcto para localizar.

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

Así que mi última pregunta es si esta es la mejor manera de determinar el paquete de la persona que llama en esta situación? ¿Hay alguna otra forma en que no haya pensado? ¿Hay algún error esperando que ocurra con mi solución actual?

+2

Esto parece depender enormemente de la implementación ... ¿Qué tan seguro está de que nada de esto cambiará en futuras versiones de Perl?¿No sería más simple y más robusto usar objetos en lugar de funciones sin procesar, tener cada objeto almacenando una función y también recordar el paquete apropiado? – Nemo

Respuesta

5

En primer lugar, puede utilizar el siguiente y no es necesario ningún cambio:

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

pero en términos generales, el siguiente es de hecho exactamente lo que quiere:

B::svref_2object($code)->STASH->NAME 

desea que el $a y $b variables del sub __PACKAGE__, por lo que desea saber el sub __PACKAGE__, y eso es exactamente lo que devuelve. Incluso se corrige el problema siguiente:

{ 
    package Utils; 
    sub mk_some_reducer { 
     ... 
     return sub { ... $a ... $b ... }; 
    } 
} 

reduce(mk_some_reducer(...), ...) 

que no soluciona todo, pero eso es imposible sin el uso de argumentos en lugar de $a y $b.

+0

Sabía que alguien iba a mencionar la solución 'goto & sub' :) Es mi solución habitual, pero en este caso los envoltorios reales necesitan localizar otras variables o deben procesar los resultados del HOF. Con respecto al comentario anterior de Nemo sobre la estabilidad de la interfaz '-> STASH-> NAME', ¿cree que es seguro suponer que la interfaz B no cambiará? –

1

En caso de que alguien los necesita, aquí están las funciones que con el tiempo me decidí a utilizar:

require B; 
use Scalar::Util 'reftype'; 
use Carp 'croak'; 

my $cv_caller = sub { 
    reftype($_[0]) eq 'CODE' or croak "not code: $_[0]"; 
    B::svref_2object($_[0])->STASH->NAME 
}; 

my $cv_local = sub { 
    my $caller = shift->$cv_caller; 
    no strict 'refs'; 
    my @ret = map \*{$caller.'::'.$_} => @_; 
    wantarray ? @ret : pop @ret 
}; 

que se utilizaría como:

my ($ca, $cb) = $code->$cv_local(qw(a b)); 

en el contexto de la pregunta original.

Cuestiones relacionadas