subrutinas - funciones en perl
¿Por qué usaría las subrutinas anónimas de Perl en lugar de una nombrada? (7)
Solo tengo curiosidad de por qué uno elegiría usar una subrutina anónima, en lugar de una nombrada, en Perl. Gracias.
Aquí hay un ejemplo de mi reescritura de la versión de Nasm.pl
# jump table to subroutines / variables
my %jump = (
id => ''id'',
xid => ''xid'',
hex_id => ''xid'',
h => /&h,
mac => /&mac,
sed => /&sed,
make => /&make,
nsis => /&nsis,
perl => /&perl,
dump => /&perl,
yaml => /&yaml,
yml => /&yaml,
json => /&json,
js => /&json,
help => /&help,
usage => sub{
require Pod::Usage;
Pod::Usage::pod2usage(
"run perldoc $0 or pod2text $0 for more information"
);
}
);
Básicamente, la única razón por la que se me ocurre es por los "calbacks" o por una mesa de saltos.
La respuesta canónica para la subrutina anónima suele ser la clasificación numérica de una matriz:
my @sorted_array = sort { $a <=> $b } @array;
El { $a <=> $b }
representa una subrutina anónima.
Las retrollamadas y los generadores vienen a la mente. Un ejemplo:
#!/usr/bin/perl
use strict;
use warnings;
sub generate_multiplier {
my ($coef) = @_;
return sub {
my ($val) = @_;
$coef * $val;
}
}
my $doubler = generate_multiplier(2);
my $tripler = generate_multiplier(3);
for my $i ( 1 .. 10 ) {
printf "%4d%4d%4d/n", $i, $doubler->($i), $tripler->($i);
}
__END__
C:/Temp> v
1 2 3
2 4 6
3 6 9
4 8 12
5 10 15
6 12 18
7 14 21
8 16 24
9 18 27
10 20 30
Las subrutinas "anónimas" son realmente similares a las subrutinas con nombre, excepto que no están unidas a un nombre en la tabla de símbolos.
sub Foo { stuff() }
BEGIN { *Foo = sub { stuff() } } # essentially equivalent
En el segundo caso, se crea la subrutina "anónima" y luego se la vincula con el nombre "Foo" en el espacio de nombre actual. El bloque BEGIN lo hace posible en tiempo de compilación, al igual que el tratamiento de una subrutina con nombre. (Es un poco más complicado porque el primer caso le da un nombre que se mostrará en una pila).
Las subrutinas anónimas son útiles cada vez que desee crear una función en tiempo de ejecución. Esto es particularmente bueno para "cierres", funciones que "recuerdan" su contexto léxico. Por ejemplo, convertir una lista en un iterador:
use 5.010;
use strict;
use warnings;
sub make_iterator {
my @list = @_;
return sub { shift @list }; # new sub that ''remembers'' @list
}
my $iter1 = make_iterator( 0 .. 10 );
my $iter2 = make_iterator( ''a'' .. ''z'' );
say $iter1->(); # ''0''
say $iter1->(); # ''1''
say $iter2->(); # ''a''
Para obtener más información sobre por qué las subrutinas anónimas son útiles, recomiendo el libro Higher Order Perl, que describe varias técnicas y aplicaciones de programación funcional en Perl.
Primero: la cosa secundaria es un sub. my $ thing = sub ... es una sub referencia almacenada en una variable.
Segundo: hay una diferencia de uso sutil:
use strict;
use warnings;
sub xx {
my $zz=1;
sub yy {
print $zz;
}
}
perl tmp.pl
Variable "$zz" will not stay shared at tmp.pl line 8.
Cambia [ sub yy
...] a [ my $yy = sub {
...] o [ local *yy = sub{
...] y la queja desaparece.
Además, para ser sincero, las referencias a los subs son más fáciles de tratar, casi lo mismo que @ x = (1,2,3) versus $ x = [1, 2, 3].
- Puede almacenar subs anónimos en matrices, hashes y escalares.
- Puedes construirlos en tiempo de ejecución
- Puede pasarlos como argumentos a otras funciones.
- Tienes que mantener las variables en el ámbito circundante.
El último punto es probablemente el más importante, porque a menudo es la faceta más inesperada de las subrutinas nombradas y anónimas en Perl. Ejemplo:
sub outer
{
my $a = 123;
sub inner
{
print $a, "/n";
}
# At this point, $a is 123, so this call should always print 123, right?
inner();
$a = 456;
}
outer(); # prints 123
outer(); # prints 456! Surprise!
Pero cambiar "interno" de una subrutina nombrada a una referencia a una subrutina anónima y funciona de una manera mucho menos sorprendente:
sub outer
{
my $a = 123;
my $inner = sub
{
print $a, "/n";
};
# At this point, $a is 123, and since the anonymous subrotine
# whose reference is stored in $inner closes over $a in the
# "expected" way...
$inner->();
$a = 456;
}
# ...we see the "expected" results
outer(); # prints 123
outer(); # prints 123
(Por supuesto, las expectativas de todos son diferentes, por lo tanto, las "citas de miedo" alrededor de "esperado").
Aquí hay un ejemplo de uso en código real (aunque debe tenerse en cuenta que la interfaz File::Find
generalmente se considera deficiente) debido a su uso de variables globales, no a su uso de subrutinas anónimas:
sub find_files
{
my @files;
my $wanted = sub
{
if($something)
{
push @files, $File::Find::name;
}
};
# The find() function called here is imported from File::Find
find({ wanted => $wanted }, $directory);
return @files;
}
Pasar una subrutina con nombre como el valor del parámetro wanted
requeriría contaminar el espacio de nombres con una rutina que solo se puede usar una vez, y definir una subrutina nombrada dentro de la subrutina find_files()
exhibiría el comportamiento "inesperado" demostrado anteriormente.
Hablo de subrutinas anónimas y por qué las usarías en Mastering Perl . En resumen, comienzas a pensar en el comportamiento como una forma más de datos al igual que piensas en cadenas o números. Cuando te sientes cómodo con esa idea, puedes hacer algunas cosas bastante sorprendentes porque puedes sacar muchas decisiones muy tarde en el programa y no tienes que contorsionar el diseño del código para tratar con anticipación cada situación que podría aparecer.
Puede escribir código sabiendo que va a ejecutar una subrutina, pero aún no sabe cuál. Confías en los pasos previos para resolver eso. Una vez que puede hacer eso, se encuentra en otro nivel de programación que se siente como si estuviera escribiendo código para crear su programa para usted. Algunos problemas de programación se vuelven mucho más fáciles de resolver de esta manera.
Y, como cualquier otra característica, puede llevar esto demasiado lejos o usarlo de manera inapropiada.