¿Es malo el "cambio" para procesar los parámetros de la subrutina de Perl?
parameters shift (8)
Asignar @_ a una lista puede traer algunas características adicionales de ayuda.
Hace que sea un poco más fácil agregar parámetros nombrados adicionales en una fecha posterior, ya que modifica su código. Algunas personas consideran que esto es una característica, similar a cómo terminar una lista o un hash con un '','' hace que sea más fácil agregar miembros en el futuro.
Si tiene el hábito de usar este modismo, cambiar los argumentos puede parecer dañino, porque si edita el código para agregar un argumento adicional, podría terminar con un error sutil, si no presta atención.
p.ej
sub do_something {
my ($foo) = @_;
}
más tarde editado para
sub do_something {
my ($foo,$bar) = @_; # still works
}
sin embargo
sub do_another_thing {
my $foo = shift;
}
Si otro colega, que utiliza la primera forma dogmáticamente (tal vez piensan que el cambio es malo) edita su archivo y lo actualiza distraídamente para leer
sub do_another_thing {
my ($foo, $bar) = shift; # still ''works'', but $bar not defined
}
y pueden haber introducido un error sutil.
Asignar a @_ puede ser más compacto y eficiente con espacio vertical, cuando tiene un pequeño número de parámetros para asignar a la vez. También le permite proporcionar argumentos predeterminados si está utilizando el estilo hash de los parámetros de funciones nombradas
p.ej
my (%arguments) = (user=>''defaultuser'',password=>''password'',@_);
Todavía consideraría una cuestión de estilo / sabor. Creo que lo más importante es aplicar un estilo u otro con coherencia, obedeciendo el principio de menor sorpresa.
Con frecuencia uso shift
para desempaquetar los parámetros de la función:
sub my_sub {
my $self = shift;
my $params = shift;
....
}
Sin embargo, muchos de mis colegas están predicando que el shift
es realmente malo. ¿Podría explicar por qué debería preferir
sub my_sub {
my ($self, $params) = @_;
....
}
para shift
?
El uso de shift
para descomprimir argumentos no es malo. Es una convención común y puede ser la manera más rápida de procesar argumentos (dependiendo de cuántos hay y cómo se pasan). Aquí hay un ejemplo de un escenario algo común donde ese es el caso: un simple acceso.
use Benchmark qw(cmpthese);
sub Foo::x_shift { shift->{''a''} }
sub Foo::x_ref { $_[0]->{''a''} }
sub Foo::x_copy { my $s = $_[0]; $s->{''a''} }
our $o = bless {a => 123}, ''Foo'';
cmpthese(-2, { x_shift => sub { $o->x_shift },
x_ref => sub { $o->x_ref },
x_copy => sub { $o->x_copy }, });
Los resultados en perl 5.8.8 en mi máquina:
Rate x_copy x_ref x_shift
x_copy 772761/s -- -12% -19%
x_ref 877709/s 14% -- -8%
x_shift 949792/s 23% 8% --
No es dramático, pero ahí está. Siempre pruebe su escenario en su versión de Perl en su hardware de destino para averiguarlo con certeza.
shift
también es útil en casos en los que desee desviar el invocant y luego llamar a un método SUPER::
, pasando el @_
restante tal cual.
sub my_method
{
my $self = shift;
...
return $self->SUPER::my_method(@_);
}
Si tuviera una serie muy larga de my $foo = shift;
operaciones en la parte superior de una función, sin embargo, podría considerar usar una copia masiva de @_
en @_
lugar. Pero en general, si tiene una función o método que requiere más que un puñado de argumentos, usar parámetros con nombre (es decir, capturar todos @_
en un hash de %args
o esperar un único argumento de referencia de hash) es un enfoque mucho mejor.
Esto es, como han dicho otros, una cuestión de gusto.
Generalmente prefiero cambiar mis parámetros a léxicos porque me da un lugar estándar para declarar un grupo de variables que se usarán en la subrutina. La verbosidad adicional me da un buen lugar para colgar comentarios. También hace que sea fácil proporcionar valores predeterminados de una manera ordenada.
sub foo {
my $foo = shift; # a thing of some sort.
my $bar = shift; # a horse of a different color.
my $baz = shift || 23; # a pale horse.
# blah
}
Si realmente está preocupado por la velocidad de llamar a sus rutinas, no descomprima sus argumentos: acceda directamente usando @_
. Tenga cuidado, esas son referencias a los datos de la persona que llama con los que está trabajando. Esta es una expresión común en POE. POE proporciona un conjunto de constantes que usa para obtener los parámetros posicionales por nombre:
sub some_poe_state_handler {
$_[HEAP]{some_data} = ''chicken'';
$_[KERNEL]->yield(''next_state'');
}
Ahora el gran error estúpido que puedes obtener si habitualmente desempaques params con shift es este:
sub foo {
my $foo = shift;
my $bar = shift;
my @baz = shift;
# I should really stop coding and go to bed. I am making dumb errors.
}
Creo que el estilo de código consistente es más importante que cualquier estilo en particular. Si todos mis compañeros de trabajo utilizaran el estilo de asignación de listas, lo usaría también.
Si mis compañeros de trabajo dijeron que había un gran problema al usar turno para desempacar, pediría una demostración de por qué es malo. Si el caso es sólido, entonces aprendería algo. Si el caso es falso, podría refutarlo y ayudar a detener la propagación del anti-conocimiento. Entonces sugeriría que determinemos un método estándar y lo sigamos para el código futuro. Incluso podría tratar de establecer una política Perl :: Critic para verificar el estándar decidido.
Hay una optimización para la asignación de listas.
La única referencia que pude encontrar es esta .
5.10.0
desactivó inadvertidamente una optimización, lo que provocó una disminución apreciable del rendimiento en la asignación de listas, como la que a menudo se utiliza para asignar parámetros de funciones de@_
. La optimización se volvió a instalar y se corrigió la regresión del rendimiento.
Este es un ejemplo de la regresión de rendimiento afectada.
sub example{
my($arg1,$arg2,$arg3) = @_;
}
No creo que el shift
sea malo. El uso de shift
muestra su disposición a nombrar variables, en lugar de usar $_[0]
.
Personalmente, uso shift
cuando solo hay un parámetro para una función. Si tengo más de un parámetro, usaré el contexto de la lista.
my $result = decode($theString);
sub decode {
my $string = shift;
...
}
my $otherResult = encode($otherString, $format);
sub encode {
my ($string,$format) = @_;
...
}
No es intrínsecamente malo, pero usarlo para extraer los argumentos de una subrutina uno por uno es comparativamente lento y requiere un mayor número de líneas de código.
No es malvado, es una especie de gusto. A menudo verá los estilos usados en conjunto:
sub new {
my $class = shift;
my %self = @_;
return bless /%self, $class;
}
Tiendo a usar shift
cuando hay un argumento o cuando quiero tratar los primeros argumentos de manera diferente que el resto.
Perl :: Critic es tu amigo aquí. Sigue los "estándares" establecidos en el libro de Damian Conway Perl Best Practices. Al ejecutarlo con --verbose 11 le da una explicación sobre por qué las cosas están mal. No desempaquetar @_ primero en sus subs es una severidad 4 (de 5). P.ej:
echo ''package foo; use warnings; use strict; sub quux { my foo= shift; my (bar,baz) = @_;};1;'' | perlcritic -4 --verbose 11
Always unpack @_ first at line 1, near ''sub quux { my foo= shift; my (bar,baz) = @_;}''.
Subroutines::RequireArgUnpacking (Severity: 4)
Subroutines that use `@_'' directly instead of unpacking the arguments to
local variables first have two major problems. First, they are very hard
to read. If you''re going to refer to your variables by number instead of
by name, you may as well be writing assembler code! Second, `@_''
contains aliases to the original variables! If you modify the contents
of a `@_'' entry, then you are modifying the variable outside of your
subroutine. For example:
sub print_local_var_plus_one {
my ($var) = @_;
print ++$var;
}
sub print_var_plus_one {
print ++$_[0];
}
my $x = 2;
print_local_var_plus_one($x); # prints "3", $x is still 2
print_var_plus_one($x); # prints "3", $x is now 3 !
print $x; # prints "3"
This is spooky action-at-a-distance and is very hard to debug if it''s
not intentional and well-documented (like `chop'' or `chomp'').
An exception is made for the usual delegation idiom
`$object->SUPER::something( @_ )''. Only `SUPER::'' and `NEXT::'' are
recognized (though this is configurable) and the argument list for the
delegate must consist only of `( @_ )''.