2011-05-23 11 views
17

Tengo un conjunto de subrutinas que se parecen a esto:Cómo crear una subrutina en Perl que acepta un bloque de código

sub foo_1($) { 
    my $name = shift; 
    my $f; 

    run_something(); 
    open($f, $name) or die ("Couldn't open $name"); 
    while (<$f>) { 
    //Something for foo_1() 
    } 
    close($f); 
    do_something_else(); 

} 

Y tengo como cuatro o más que tienen el mismo aspecto, lo único que cambia es el cuerpo del bloque while. Me gustaría resumir esto y dejar de copiar y pegar el código.

  • ¿Hay alguna forma de codificar una subrutina que acepte un bloque de código y la ejecute?

Para dar más contexto, las diferentes subrutinas foo son una máquina de estados finitos (FSM) diferente que lee los contenidos de diferentes archivos y alimenta los datos a una referencia hash. Tal vez hay algo más inteligente que hacer que lo que estoy tratando de lograr.

+0

En general, los prototipos de función no son necesarios y considero que son un poco inútiles. – jiggy

+0

@jiggy ¿Puedes elaborar un poco, por favor? –

+1

=> En este caso, usar el prototipo '($)' puede no significar lo que piensas que significa. Significa "darme un argumento", pero también significa "imponer un contexto escalar en ese argumento". Entonces, si tuvieras una matriz con un elemento y se llamara 'foo_1 @ array', entonces 'foo_1' pasaría el número' 1', que es el recuento de los elementos en la matriz. Para obtener realmente el primer argumento, necesitarás llamarlo como 'foo_1 $ array [0]'. Si no tenía el prototipo, podría haberlo llamado 'foo_1 @ array' y habría funcionado correctamente. –

Respuesta

34

Perl ofrece un sistema llamado prototipos subrutina que le permiten escribir submarinos usuario que consiguen evalúan de manera similar a las funciones incorporadas. Los editores que desea emular son map, grep o sort, que cada uno puede tomar un bloque como primer argumento.

de hacer eso con los prototipos, se utiliza sub name (&) {...} donde el & dice Perl que el primer argumento de la función es o bien un bloque (con o sin sub), o una subrutina literal \&mysub. El prototipo (&) especifica uno y solo un argumento, si necesita pasar varios argumentos después del bloque de código, puede escribirlo como (&@), lo que significa un bloque de código seguido de una lista.

sub higher_order_fn (&@) { 
    my $code = \&{shift @_}; # ensure we have something like CODE 

    for (@_) { 
     $code->($_); 
    } 
} 

Esa subrutina ejecutará el bloque pasado en cada elemento de la lista pasada. El \&{shift @_} parece un poco críptico, pero lo que hace es quitar el primer elemento de la lista, que debería ser un bloque de código. El &{...} elimina la referencia del valor como una subrutina (invocando cualquier sobrecarga), y luego el \ toma inmediatamente la referencia al mismo. Si el valor fue una REF del CÓDIGO, entonces se devuelve sin cambios. Si se trataba de un objeto sobrecargado, se convierte en código. Si no se puede forzar a CODE, se produce un error.

para llamar a esta subrutina, usted escribiría:

higher_order_fn {$_ * 2} 1, 2, 3; 
# or 
higher_order_fn(sub {$_ * 2}, 1, 2, 3); 

El (&@) prototipo que permite escribir el argumento como una map/grep como el bloqueo sólo funciona cuando se utiliza la función de orden superior como una función. Si lo está utilizando como un método, debe omitir el prototipo y escribirlo de esta manera:

sub higher_order_method { 
    my $self = shift; 
    my $code = \&{shift @_}; 
    ... 
    $code->() for @_; 
} 
... 
$obj->higher_order_method(sub {...}, 'some', 'more', 'args', 'here'); 
+1

Gracias por la respuesta, Eric. –

+0

... continúa (presiono Intro por error) Me gustaría saber por qué usa \ & {shift @_}. ¿Por qué es esto diferente de las otras respuestas que la gente me dio? Soy bastante nuevo para Perl (pero he codificado algún tiempo en Haskell, Java, Scheme y C). Gracias. –

+0

es una forma concisa de verificar que el argumento sea realmente una referencia de código. ampliado significa algo como 'do {my $ x = shift; ref $ x eq 'CÓDIGO'? $ x: sobrecarga :: Sobrecargado ($ _ [0], '& {}')? \ & $ x: die "no es una referencia de código"} '. El prototipo es una restricción de tiempo de compilación que busca una referencia de código para usted, pero puede anularse llamando al sub con un símbolo '&' (o llamando al código como método). El '\ & {shift @_}' es una verificación adicional contra ese bypass. –

11
sub bar { 
    my ($coderef) = @_; 
    ⁝ 
    $coderef->($f, @arguments); 
    ⁝ 
} 

bar(sub { my ($f) = @_; while … }, @other_arguments); 

o tal vez un poco menos enredada con un llamado coderef:

my $while_sub = sub { 
    my ($f) = @_; 
    while … 
    ⁝ 
}; 
bar($while_sub, @other_arguments); 

Edición: El libro Higher-Order Perl está llena de este tipo de programación.

+0

Muchas gracias. Creo que leeré ese libro. ¡Suena divertido! –

9

Quiere el prototipo &.

sub foo(&@) { 
    my ($callback) = shift; 
    ... 
    $callback->(...); 
    ... 
} 

hace

foo { ... } ...; 

equivalente a

foo(sub { ... }, ...); 
Cuestiones relacionadas