2009-08-24 10 views
7

Así que tengo una clase de Perl. Tiene un método sort(), y yo quiero que sea más o menos idéntica a la incorporada en el sort() función:Perl variable cuestión de alcance

$object->sort(sub ($$) { $_[0] <=> $_[1] }); 

Pero no puedo hacer:

$object->sort(sub { $a <=> $b }); 

Debido alcance. Pero el módulo List :: Util hace esto con reduce(). Miré el módulo List :: Util, y hacen algunas cosas bastante desagradables con no strict 'vars' para que esto suceda. Intenté eso, pero fue en vano.

Tengo entendido que reduce() funciona de la forma en que lo hace porque se exporta al espacio de nombres apropiado, y por lo tanto mi clase no puede hacerlo ya que la función está firmemente en otro espacio de nombres. ¿Es correcto o hay alguna forma (indudablemente más espantosa y desaconsejable) de hacerlo en mi situación?

Respuesta

8

Bueno, las otras dos respuestas son a la vez la mitad derecha. He aquí una solución de trabajo que realmente géneros:

package Foo; 

use strict; 
use warnings; 

sub sort { 
    my ($self, $sub) = @_; 

    my ($pkg) = caller; 
    my @x = qw(1 6 39 2 5); 
    print "@x\n"; 
    { 
     no strict 'refs'; 
     @x = sort { 
      local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); 
      $sub->(); 
     } @x; 
    } 
    print "@x\n"; 

    return; 
} 


package main; 

use strict; 
use warnings; 

my $foo = {}; 
bless $foo, 'Foo'; 

$foo->sort(sub { $a <=> $b }); 
# 1 6 39 2 5 
# 1 2 5 6 39 

Es de suponer que habría ordenar algunos datos que en realidad es parte del objeto.

Necesita la magia caller para localizar $a y $b en el paquete de la persona que llama, que es donde Perl los va a buscar. Está creando variables globales que solo existen mientras se está llamando a ese sub.

Tenga en cuenta que obtendrá un 'nombre usado una sola vez' con warnings; Estoy seguro de que hay algunos aros que puedes saltar para evitar esto, de alguna manera.

+2

Eso puede ser lo suficientemente bueno para sus propósitos, pero es frágil. No hay garantía de que la función de comparación pertenezca al mismo paquete que la persona que llama del método 'sort'. Ahí es donde interviene Sub :: Identify. – cjm

+0

@cjm - Esto es cierto, y definitivamente examinaré Sub :: Identify, pero mi mayor problema es hacerlo funcionar, en lugar de hacerlo funcionar en el caso general. Las soluciones específicas son mejores que las fallas generales. Sin embargo, combinar esta respuesta con la tuya me daría una solución general, que es algo bueno. –

+1

Aunque resulta que el problema 'sort' tiene el mismo problema. Se supone que la función de comparación proviene del mismo paquete que la persona que llama. Entonces, si puedes vivir con eso, guardas una dependencia en Sub :: Identify. (O podría exigir condicionalmente Sub :: Identify, y recurrir a 'calller' si no está instalado. Pero eso es más trabajo.) – cjm

1

Puede utilizar the local operator para establecer los valores de $a y $b durante la duración de la llamada del subprograma:

sub sample 
{ 
    my $callback = shift; 
    for (my $i = 0; $i < @_; $i += 2) { 
     local ($a, $b) = @_[$i, $i + 1]; 
     $callback->(); 
    } 
}  

sample sub { print "$a $b\n" }, qw(a b c d e f g h i j); 

Si usted tiene una subrutina de ordinario, en lugar de un método, entonces usted puede hacer que sea aún más como sort, por lo que no necesita usar sub antes de su función de devolución de llamada. Utilizar un prototipo de la función:

sub sample (&@) 

Entonces llaman a llamarlo así:

sample { print "$a $b\n" } qw(a b c d e f g h i j); 

métodos, sin embargo, no están influenciados por prototipos.

+0

Está preguntando específicamente sobre un 'método', no un sub simple. –

+3

Eso no funcionará si llama al método desde fuera de la clase. Está localizando la * clase * $ a y $ b, no la de la persona que llama. – cjm

+0

Ah. Pensé que había visto esto hecho, pero supongo que no. Mi impresión de Perlvar fue que '$ a' y' $ b' eran lo suficientemente mágicos como para que "simplemente funcionaran" en esta situación. –

3

Puede usar Sub::Identify para encontrar el paquete (al que llama stash_name) asociado con el valor del código. Luego configure $ a y $ b en ese paquete según sea necesario. Es posible que necesite usar no strict 'refs' en su método para que funcione.

Aquí es la respuesta de Evee modificado para que funcione en el caso general:

use strict; 
use warnings; 

package Foo; 

use Sub::Identify 'stash_name'; 

sub sort { 
    my ($self, $sub) = @_; 

    my $pkg = stash_name($sub); 
    my @x = qw(1 6 39 2 5); 
    print "@x\n"; 
    { 
     no strict 'refs'; 
     @x = sort { 
      local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); 
      $sub->(); 
     } @x; 
    } 
    print "@x\n"; 

    return; 
} 


package Sorter; 

sub compare { $a <=> $b } 

package main; 

use strict; 
use warnings; 

my $foo = {}; 
bless $foo, 'Foo'; 

$foo->sort(\&Sorter::compare); 

$foo->sort(sub { $b <=> $a }); 
+1

Lista :: Util solo usa 'llamador'. Supongo que eso no es suficiente, en el caso general, ¿verdad? Si la persona que llama pasa una función de otro paquete, List :: Util establecería el '$ a' del que llama en lugar de la función. –

+0

La versión de List :: Util en 5.10.1 usa código XS que básicamente hace lo mismo que Sub :: Identify para descubrir el paquete al que pertenece el coderef. – cjm

+0

Alternativamente, podría reescribir mi módulo en XS, perdiendo así una dependencia _y_ dándome la oportunidad de aprender XS. Sin embargo, mientras tanto, investigaré esto. –