2009-12-16 22 views
51

Guardo el nombre de la subrutina que quiero llamar en el tiempo de ejecución en una variable llamada $ action. Luego lo uso para llamar a ese submarino en el momento correcto:¿Cómo puedo llamar elegantemente a una subrutina Perl cuyo nombre se mantiene en una variable?

&{\&{$action}}(); 

Funciona bien. La única cosa que no me gusta es que es feo y cada vez que lo hago, me siento en deuda con añadir un comentario para que el próximo desarrollador:

# call the sub by the name of $action 

Alguien sabe una forma más bonita de hacer esto?


ACTUALIZACIÓN: La idea era evitar tener que mantener una tabla de despacho cada vez que he añadido un nuevo sub exigible, ya que soy el único desarrollador, no estoy preocupado por otros programadores seguir o no seguir el 'reglas'. Sacrificando un poco de seguridad para mi conveniencia. En cambio, mi módulo de despacho verificaría $ action para asegurarme de que 1) es el nombre de una subrutina definida y no un código malicioso para ejecutar con eval, y 2) que no se ejecutará sub-precedido por un guión bajo, que sería marcados como subs internos solamente por esta convención de nomenclatura.

¿Alguna idea sobre este enfoque? Las subrutinas incluidas en la lista blanca en la tabla de envío es algo que siempre olvidaré, y mis clientes preferirían que yo me equivocara del lado de "funciona" que "es muy seguro". (Tiempo muy limitado para desarrollar aplicaciones)


INFORME FINAL DE ACTUALIZACIÓN: Creo que he decidido sobre una mesa de despacho después de todo. Aunque me gustaría saber si alguien que lee esta pregunta alguna vez intentó eliminar una y cómo lo hicieron, debo inclinarme ante la sabiduría colectiva aquí. Gracias a todos, muchas excelentes respuestas.

+3

Date cuenta que cuando se toma una referencia a algo que de inmediato eliminar la referencia, puede saltarse los pasos intermedios. :) –

+1

@brian: generalmente (y definitivamente en este caso), pero vea http://stackoverflow.com/questions/1836178/how-do-i-call-a-function-name-that-is-stored-in -a-hash-in-perl/1836356 # 1836356 para un contraejemplo. :) – Ether

+1

Una tabla de envío también significa que puede 'usar terminante', que generalmente es algo bueno. – OrangeDog

Respuesta

76

En lugar de almacenar nombres de subrutinas en una variable y llamarlos, una mejor forma de hacerlo es usar un hash de referencias de subrutinas (también conocido como dispatch table).)

my %actions = (foo => \&foo, 
       bar => \&bar, 
       baz => sub { print 'baz!' } 
       ... 
      ); 

A continuación, puede llamar a la correcta fácilmente:

$actions{$action}->(); 

También se puede añadir un poco de comprobación para asegurarse de $action es una clave válida en el hash, y así sucesivamente.

En general, debe evitar referencias simbólicas (lo que está haciendo ahora) ya que causan todo tipo de problemas. Además, el uso de referencias de subrutinas reales funcionará con strict encendido.

+1

He repetido mi comentario aquí de otra respuesta a continuación, ya que esta respuesta tiene, con mucho, el puntaje más alto. en caso de que ayude a alguien, si también desea pasar parámetros a la subrutina, simplemente colóquelos entre() - es decir, lo mismo que haría con un sub normal. p.ej. '$ actions {$ action} -> ($ parmVariable, 11, 'a');' Los parámetros tendrían que ser coherentes en todas las sub rutinas posibles a las que se llama, aunque los valores pueden provenir de variables, por supuesto. –

0

Si solo está en un programa, escriba una función que llame a una subrutina utilizando un nombre de variable y solo tenga que documentarla/disculparse una vez.

11

No estoy seguro de entender lo que quieres decir. (Creo que este es otro en un grupo reciente de preguntas "¿Cómo puedo usar una variable como nombre de variable?", Pero tal vez no).

En cualquier caso, debería poder asignar una subrutina completa a un variable (como referencia), y luego llamar sin rodeos:

# create the $action variable - a reference to the subroutine 
my $action = \&preach_it; 
# later - perhaps much later - I call it 
$action->(); 

sub preach_it { 
    print "Can I get an amen!\n" 
} 
21

Sólo &$action(), pero por lo general es más agradable de usar coderefs desde el principio, o utilizar un hash despachador. Por ejemplo:

my $disp = {foo => \&some_sub, bar => \&some_other_sub }; 
$disp->{'foo'}->(); 
+0

Utilizando la solución de & $ action Obtengo: No se puede usar una cadena ("main :: instructores") como una subrutina ref mientras que "strict refs". Entonces, ¿la gimnasia que estoy haciendo solo es porque estoy estrictamente excitado? – Marcus

+6

Sí, este tipo de uso es lo que 'use strict' previene (entre otros). Utilice 'no estrictos' refs '' para permitirlo, pero de nuevo, las referencias reales o despachadores son usualmente mucho más claros. – Harmen

+3

La respuesta de la tabla de envío, que utiliza referencias reales (en lugar de simbólicas), por supuesto funcionará con 'strict' activado, que es otra ventaja de hacerlo de esa manera. – friedo

6

Hago algo similar. Lo dividí en dos líneas para hacerlo un poco más identificable, pero no es mucho más bonito.

my $sub = \&{$action}; 
$sub->(); 

No conozco una forma más correcta o más bonita de hacerlo. Por lo que vale, tenemos un código de producción que hace lo que está haciendo, y funciona sin tener que deshabilitar use strict.

+1

y en caso de que ayude a alguien, si también desea pasar parámetros a la subrutina, simplemente colóquelos entre() - es decir, lo mismo que lo haría con un submarino normal. p.ej. '$ sub -> ($ parmVariable, 11, 'a');' –

+0

Llamar esto reflexivamente desde dentro de un sub; my $ this = \ & {(llamador (0)) [3]}; $ return = $ this ->(); $ return. = $ This -> (\ @ flags, $ arguments, \% foo); –

10

Lo más importante es: ¿por qué quieres usar la variable como nombre de la función. ¿Qué pasará si será 'eval'? ¿Hay una lista de funciones que se pueden usar? ¿O puede ser alguna función? Si existe una lista, ¿cuánto dura?

En general, la mejor manera de manejar estos casos es utilizar tablas de despacho:

my %dispatch = (
    'addition' => \&some_addition_function, 
    'multiplication' => sub { $self->call_method(@_) }, 
); 

Y luego simplemente:

$dispatch{ $your_variable }->('any', 'args'); 
14

Huh? Se puede decir simplemente

$action->() 

Ejemplo:

sub f { return 11 } 
    $action = 'f'; 
    print $action->(); 


    $ perl subfromscalar.pl 
    11 

Construcciones como

'f'->()  # equivalent to &f() 

también trabajar.

+9

Si 'utiliza strict' (y por supuesto que lo hace), tendrá que decir' no strict 'refs'' o simplemente 'use strict qw (vars subs)' para usar esta sintaxis. – mob

1

ya sea para uso

&{\&{$action}}(); 

O utilice eval para ejecutar la función:

eval("$action()"); 
1

lo hice de esta manera:

@func = qw(cpu mem net disk); 
foreach my $item (@func){ 
    $ret .= &$item(1); 
} 
0

utilicé esto: a mí me funciona .

(\$action)->(); 

O puede utilizar 'hacer', bastante similar con críticas anteriores:

$p = do { \&$conn;}; 
$p->(); 
3

Cada paquete en Perl ya es una tabla hash. Puede agregar elementos y hacer referencia a ellos mediante las operaciones hash normales. En general, no es necesario duplicar la funcionalidad mediante una tabla hash adicional.

#! /usr/bin/perl -T 
use strict; 
use warnings; 

my $tag = 'HTML'; 

*::->{$tag} = sub { print '<html>', @_, '</html>', "\n" }; 

HTML("body1"); 

*::->{$tag}("body2"); 

La impresora de código:

 
<html>body1</html> 
<html>body2</html> 

Si necesita un espacio de nombres separado, puede definir un paquete dedicado.

Consulte perlmod para obtener más información.

+0

Es una página larga para enlazar, pero creo que te estás refiriendo a la sección [Tablas de símbolos] (http://perldoc.perl.org/perlmod.html#Symbol-Tables). – Teepeemm

Cuestiones relacionadas