2010-03-31 19 views
26

Para fines de aprendizaje, estoy jugando con la idea de crear programas controlados por eventos en Perl y noté que podría ser bueno si una subrutina registrada como un controlador de eventos pudiera, en caso de error, programar otra llamada a sí mismo para un momento posterior. Hasta el momento, no tengo llegar a algo como esto:En Perl, ¿cómo puede una subrutina obtener un coderef que apunta a sí mismo?

my $cb; 
my $try = 3; 
$cb = sub { 
    my $rc = do_stuff(); 
    if (!$rc && --$try) { 
     schedule_event($cb, 10); # schedule $cb to be called in 10 seconds 
    } else { 
     do_other_stuff; 
    } 
}; 
schedule_event($cb, 0); # schedule initial call to $cb to be performed ASAP 

¿Hay alguna manera de que el código dentro de la sub pueden acceder a la coderef a la sub por lo que podía hacer sin necesidad de utilizar una variable adicional? Me gustaría programar la llamada inicial de esta manera.

schedule_event(sub { ... }, 0); 

por primera idea de utilizar caller(0)[3], pero esto sólo me da un nombre función, (si no hay nombre), no una referencia de código que tiene una almohadilla que se le atribuye.

+1

Pregunta realmente genial. –

+4

Si está haciendo esto mucho en la misma ejecución del programa, tendrá una pérdida de memoria debido a la referencia circular. Puede usar Scalar :: Util :: weaken() para evitar esto, o usar Sub :: Current o el Y-combinator como se sugiere a continuación. Ver http://use.perl.org/~Aristotle/journal/30896 para una discusión. Si este código no está en un entorno persistente, es probable que su código anterior sea correcto. – runrig

+0

runrig: Gracias por el enlace. Mi cabeza está girando ahora. :-) Tal vez voy a aprender algo ... – hillu

Respuesta

14

Creo que Sub::Current solucionará su problema.

+0

Gracias. Sí, aparentemente esto hace lo que quiero. Lástima que necesita hurgar en el interior de Perl para hacer eso ... – hillu

5

Si no cambia el valor de $cb nuevamente, puede usarlo. Si no, defina un escalar para mantener eso y no lo cambie nunca más. Por ejemplo:

my $cb = do { 
    my $sub; 
    $sub = sub { contents using $sub here } 
} 
14

para obtener una referencia a la subrutina actual sin necesidad de utilizar una variable adicional, puede utilizar una herramienta de programación funcional, la Y-Combinator, que básicamente se abstrae el proceso de crear el cierre. Aquí está una versión perlish:

use Scalar::Util qw/weaken/; 

sub Y (&) { 
    my ($code, $self, $return) = shift; 
    $return = $self = sub {$code->($self, @_)}; 
    weaken $self; # prevent a circular reference that will leak memory 
    $return; 
} 

schedule_event(Y { my $self = shift; ... }, 0); 
+0

¿Es esto correcto? No estoy seguro para qué es $ curry. Al principio pensé que mi respuesta era diferente, pero ahora creo que su 3 ° "$ curry" debería ser "$ code" y debería deshacerse del $ curry. Y esta respuesta creo que le daría una referencia circular con una pérdida de memoria. – runrig

+0

@runrig => si solo pasa la referencia del código original al sub, todas las llamadas subsiguientes que usen esa referencia ya no recibirán coderef como primer argumento. Había una referencia circular en el código que tenía. Lo he actualizado para corregir el problema. –

+0

Sí, lo tengo unos 5 minutos después de que me fui. He actualizado mi respuesta también :-) – runrig

4

El uso de un combinador de punto fijo, se puede escribir su función cb $ como si el primer argumento era la propia función:

sub U { 
    my $f = shift; 
    sub { $f->($f, @_) } 
} 

my $cb = sub { 
    my $cb = shift; 
    ... 
    schedule_event(U($cb), 10); 
    ... 
} 

schedule_event(U($cb), 0); 
+0

su segunda llamada a 'schedule_event' no se pasará una copia de $ cb que se recibe como su primer argumento. He actualizado mi respuesta para mostrar una versión que ya no pierde la memoria. –

+0

Tiene razón, una función de devolución de llamada se está transfiriendo a otra función en lugar de recibir una llamada desde el submarino, por lo que el subcontratista debe ser envuelto cada vez. Actualizado. – runrig

13

__SUB__ se ha añadido en 5.16 , proporcionando esta usabilidad.

Cuestiones relacionadas