programacion - ¿Hay alguna diferencia entre el desplazamiento de Perl y la asignación de @_ para los parámetros de la subrutina?
perl pdf (9)
Vamos a ignorar por un momento la mejor práctica de Damian Conway de no más de tres parámetros posicionales para una subrutina determinada.
¿Hay alguna diferencia entre los dos ejemplos a continuación en cuanto a rendimiento o funcionalidad?
Usando shift
:
sub do_something_fantastical {
my $foo = shift;
my $bar = shift;
my $baz = shift;
my $qux = shift;
my $quux = shift;
my $corge = shift;
}
Usando @_
:
sub do_something_fantastical {
my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
}
Siempre que ambos ejemplos sean los mismos en términos de rendimiento y funcionalidad, ¿qué piensa la gente sobre un formato sobre el otro? Obviamente, el ejemplo que usa @_
es menos líneas de código, pero ¿no es más legible usar shift
como se muestra en el otro ejemplo? Las opiniones con buen razonamiento son bienvenidas.
Al menos en mis sistemas, parece depender de la versión de Perl y la arquitectura:
#!/usr/bin/perl -w
use strict;
use warnings;
use autodie;
use Benchmark qw( cmpthese );
print "Using Perl $] under $^O/n/n";
cmpthese(
-1,
{
shifted => ''call( /&shifted )'',
list_copy => ''call( /&list_copy )'',
}
);
sub call {
$_[0]->(1..6); # Call our sub with six dummy args.
}
sub shifted {
my $foo = shift;
my $bar = shift;
my $baz = shift;
my $qux = shift;
my $quux = shift;
my $corge = shift;
return;
}
sub list_copy {
my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
return;
}
Resultados:
Using Perl 5.008008 under cygwin
Rate shifted list_copy
shifted 492062/s -- -10%
list_copy 547589/s 11% --
Using Perl 5.010000 under MSWin32
Rate list_copy shifted
list_copy 416767/s -- -5%
shifted 436906/s 5% --
Using Perl 5.008008 under MSWin32
Rate shifted list_copy
shifted 456435/s -- -19%
list_copy 563106/s 23% --
Using Perl 5.008008 under linux
Rate shifted list_copy
shifted 330830/s -- -17%
list_copy 398222/s 20% --
Por lo tanto, parece que list_copy es generalmente un 20% más rápido que el cambio, excepto en Perl 5.10, donde el cambio es realmente un poco más rápido.
Tenga en cuenta que estos fueron resultados derivados rápidamente. Las diferencias de velocidad reales serán mayores que las enumeradas aquí, ya que Benchmark también cuenta el tiempo necesario para llamar y devolver las subrutinas, lo que tendrá un efecto moderador en los resultados. No he hecho ninguna investigación para ver si Perl está haciendo algún tipo especial de optimización. Su experiencia puede ser diferente.
Pablo
Me imagino que el ejemplo de cambio es más lento que usar @_ porque son 6 llamadas a función en lugar de 1. Ya sea que sea notable o incluso mensurable es una pregunta diferente. Tirar cada uno en un bucle de iteraciones de 10k y medir el tiempo.
En cuanto a la estética, prefiero el método @_. Parece que sería demasiado fácil alterar el orden de las variables utilizando el método shift con un corte y pegado accidental. Además, he visto a muchas personas hacer algo como esto:
sub do_something {
my $foo = shift;
$foo .= ".1";
my $baz = shift;
$baz .= ".bak";
my $bar = shift;
$bar .= ".a";
}
Esto, en mi humilde opinión, es muy desagradable y podría conducir fácilmente a errores, por ejemplo, si corta el bloque baz y lo pega debajo del bloque de barra. Estoy a favor de definir variables cerca de donde se usan, pero creo que la definición de las variables aprobadas en la parte superior de la función tiene prioridad.
Prefiero descomprimir @_ como una lista (su segundo ejemplo). Sin embargo, como todo en Perl, hay casos en los que usar shift puede ser útil. Por ejemplo, los métodos passthru que se pretenden anular en una clase base pero desea asegurarse de que las cosas sigan funcionando si no se anulan.
package My::Base;
use Moose;
sub override_me { shift; return @_; }
Prefiero usar
sub do_something_fantastical {
my ( $foo, $bar, $baz, $qux, $quux, $corge ) = @_;
}
Porque es más legible. Cuando no se llama a este código con mucha frecuencia, vale la pena hacerlo. En casos muy raros, desea hacer una función llamada a menudo y usar @_ directamente. Es efectivo solo para funciones muy cortas y debe estar seguro de que esta función no evolucionará en el futuro (función Escribir una vez). En este caso, realicé una evaluación comparativa en 5.8.8 que para un solo parámetro es un cambio más rápido que $ _ [0] pero para dos parámetros que usan $ _ [0] y $ _ [1] es más rápido que el cambio, cambio.
sub fast1 { shift->call(@_) }
sub fast2 { $_[0]->call("a", $_[1]) }
Pero volviendo a su pregunta. También prefiero @_ asignación en una fila por turnos para muchos parámetros de esta manera
sub do_something_fantastical2 {
my ( $foo, $bar, $baz, @rest ) = @_;
...
}
Cuando Suspect @rest no será demasiado grande. En otro caso
sub raise {
my $inc = shift;
map {$_ + $inc} @_;
}
sub moreSpecial {
my ($inc, $power) = (shift(), shift());
map {($_ + $inc) ** $power} @_;
}
sub quadratic {
my ($a, $b, $c) = splice @_, 0, 3;
map {$a*$_*$_ + $b*$_ + $c} @_;
}
En casos raros, necesito una optimización de la cola de llamada (por supuesto, por supuesto), entonces debo trabajar directamente con @_, que para una función corta vale la pena.
sub _switch #(type, treeNode, transform[, params, ...])
{
my $type = shift;
my ( $treeNode, $transform ) = @_;
unless ( defined $type ) {
require Data::Dumper;
die "Broken node: " . Data::Dumper->Dump( $treeNode, [''treeNode''] );
}
goto &{ $transform->{$type} } if exists $transform->{$type};
goto &{ $transform->{unknown} } if exists $transform->{unknown};
die "Unknown type $type";
}
sub switchExTree #(treeNode, transform[, params, ...])
{
my $treeNode = $_[0];
unshift @_, $treeNode->{type}; # set type
goto &_switch; # tail call
}
sub switchCompact #(treeNode, transform[, params, ...])
{
my $treeNode = $_[0];
unshift @_, (%$treeNode)[0]; # set type given as first key
goto &_switch; # tail call
}
sub ExTreeToCompact {
my $tree = shift;
return switchExTree( $tree, /%transformExTree2Compact );
}
sub CompactToExTree {
my $tree = shift;
return switchCompact( $tree, /%transformCompact2ExTree );
}
Donde% transformExTree2Compact y% transformCompact2ExTree son hashes con tipo en clave y código ref en valor, que pueden interceptar switchExTree o switchCompact self. Pero este enfoque rara vez es realmente necesario y debe evitar que valga menos los dedos de la universidad .
En conclusión, la legibilidad y la capacidad de mantenimiento se deben especialmente en perl y la asignación de @_ en una fila es mejor. Si desea establecer los valores predeterminados, puede hacerlo justo después.
Usualmente uso la primera versión. Esto se debe a que generalmente necesito verificar los errores junto con los cambios, lo cual es más fácil de escribir. Decir,
sub do_something_fantastical {
my $foo = shift || die("must have foo");
my $bar = shift || 0; # $bar is optional
# ...
}
Sospecho que si estás haciendo el equivalente (aproximado) de:
push @bar, shift @_ for (1 :: $big_number);
Entonces estás haciendo algo mal. Casi siempre uso my ($foo, $bar) = @_;
formulario porque me he disparado en el pie usando este último unas cuantas veces ...
yo prefiero
sub factorial{
my($n) = @_;
...
}
Por la simple razón de que Komodo podrá decirme cuáles son los argumentos, cuando vaya a usarlo.
Hay una diferencia funcional. El cambio modifica @_
, y la asignación de @_
no. Si no necesita usar @_
después, esa diferencia probablemente no le importe. Intento usar siempre la asignación de lista, pero a veces uso shift
.
Sin embargo, si comienzo con shift
, así:
my( $param ) = shift;
A menudo creo este error:
my( $param, $other_param ) = shift;
Eso es porque no uso el shift
tan a menudo, así que me olvido de pasar al lado derecho de la tarea para cambiar eso a @_
. Ese es el punto de la mejor práctica al no usar shift
. Podría hacer líneas separadas para cada shift
como lo hizo en su ejemplo, pero eso es simplemente tedioso.
La mejor manera, en mi humilde opinión, es una ligera mezcla de los dos, como en la nueva función en un módulo:
our $Class;
sub new {
my $Class = shift;
my %opts = @_;
my $self = /%opts;
# validate %opts for correctness
...
bless $self, $Class;
}
Luego, todos los argumentos de llamada al constructor se pasan como un hash, lo que hace que el código sea mucho más legible que solo una lista de parámetros.
Además, como dijo Brian , la variable @_
no está modificada, lo que puede ser útil en casos inusuales.