subrutinas funciones perl subroutine shift

funciones - en perl, ¿es una mala práctica llamar a múltiples subrutinas con argumentos predeterminados?



funciones en perl (7)

Estoy aprendiendo perl y entiendo que es una práctica común y aceptada desempaquetar argumentos de subrutinas usando shift. También entiendo que es una práctica común y aceptable omitir los argumentos de la función para usar la matriz @_ predeterminada.

Teniendo en cuenta estas dos cosas, si llama a una subrutina sin argumentos, @_ puede (y lo hará, si usa shift) cambiarse. ¿Esto significa que llamar a otra subrutina con argumentos predeterminados o, de hecho, usar la matriz @_ después de esto, se considera una mala práctica? Considera este ejemplo:

sub total { # calculate sum of all arguments my $running_sum; # take arguments one by one and sum them together while (@_) { $running_sum += shift; } $running_sum; } sub avg { calculate the mean of given arguments if (@_ == 0) { return } my $sum = &total; # gets the correct answer, but changes @_ $sum / @_ # causes division by zero, since @_ is now empty }

Mi instinto me dice que el uso de los cambios de desempaquetar sería realmente una mala práctica, a menos que se suponga que su subrutina cambie los argumentos pasados, pero he leído en varios lugares, incluido el Desbordamiento de pila, que no es una mala práctica.

Entonces, la pregunta es: si usar shift es una práctica común, ¿debería asumir siempre que la lista de argumentos pasada podría cambiar, como un efecto secundario de la subrutina (como la subrutina &total en el ejemplo citado)? ¿Existe tal vez una forma de pasar argumentos por valor, de modo que pueda estar seguro de que la lista de argumentos no se modifique, por lo que podría usarla nuevamente (como en la subrutina &avg en el texto citado)?


Aquí hay algunos ejemplos donde el uso cuidadoso de @_ importa.

1. Hash-y Argumentos

A veces desea escribir una función que puede tomar una lista de pares clave-valor, pero uno es el uso más común y desea que esté disponible sin necesidad de una clave. Por ejemplo

sub get_temp { my $location = @_ % 2 ? shift : undef; my %options = @_; $location ||= $options{location}; ... }

Así que ahora, si llama a la función con un número impar de argumentos, el primero es la ubicación. Esto permite get_temp(''Chicago'') o get_temp(''New York'', unit => ''C'') o incluso get_temp( unit => ''K'', location => ''Nome, Ak'') . Esta puede ser una API más conveniente para sus usuarios. Al cambiar el argumento impar, ahora @_ es una lista uniforme y puede asignarse a un hash.

2. Despacho

Digamos que tenemos una clase que deseamos poder enviar métodos por nombre (posiblemente, AUTOLOAD podría ser útil, lo haremos a mano). Quizás este es un script de línea de comandos donde los argumentos son métodos. En este caso definimos dos métodos de envío, uno "limpio" y otro "sucio". Si llamamos con la bandera -c obtenemos la limpia. Estos métodos encuentran el método por nombre y lo llaman. La diferencia es cómo. El sucio se deja a sí mismo en la traza de la pila, el limpio tiene que ser más cuchillo, pero se despacha sin estar en la traza de la pila. Hacemos un método de death que nos da ese rastro.

#!/usr/bin/env perl use strict; use warnings; package Unusual; use Carp; sub new { my $class = shift; return bless { @_ }, $class; } sub dispatch_dirty { my $self = shift; my $name = shift; my $method = $self->can($name) or confess "No method named $name"; $self->$method(@_); } sub dispatch_clean { my $self = shift; my $name = shift; my $method = $self->can($name) or confess "No method named $name"; unshift @_, $self; goto $method; } sub death { my ($self, $message) = @_; $message ||= ''died''; confess "$self->{name}: $message"; } package main; use Getopt::Long; GetOptions ''clean'' => /my $clean, ''name=s'' => /(my $name = ''Robot''); my $obj = Unusual->new(name => $name); if ($clean) { $obj->dispatch_clean(@ARGV); } else { $obj->dispatch_dirty(@ARGV); }

Así que ahora si llamamos a ./test.pl para invocar el método de muerte

$ ./test.pl death Goodbye Robot: Goodbye at ./test.pl line 32 Unusual::death(''Unusual=HASH(0xa0f7188)'', ''Goodbye'') called at ./test.pl line 19 Unusual::dispatch_dirty(''Unusual=HASH(0xa0f7188)'', ''death'', ''Goodbye'') called at ./test.pl line 46

Pero vemos a dispatch_dirty en la traza. Si, en cambio, llamamos a ./test.pl -c , ahora usamos el despachador limpio y obtenemos

$ ./test.pl -c death Adios Robot: Adios at ./test.pl line 33 Unusual::death(''Unusual=HASH(0x9427188)'', ''Adios'') called at ./test.pl line 44

La clave aquí es el goto (no el goto malvado) que toma la referencia de subrutina e inmediatamente cambia la ejecución a esa referencia, utilizando la actual @_ . Esta es la razón por la que tengo que unshift @_, $self para que el invocante esté listo para el nuevo método.


Debe evitar el uso de la &func; estilo de llamada a menos que tenga una buena razón, y confíe en que otros hagan lo mismo.

Para proteger su @_ contra la modificación por parte de una persona llamada, simplemente haga &func() o func .


En general, el shift de los argumentos está bien, al usar las funciones de & sigil para llamar no. (Excepto en algunas situaciones muy específicas que probablemente nunca encontrarás).

Su código podría reescribirse, de modo que el total no shift de @_ . Usar un for-loop puede ser incluso más eficiente.

sub total { my $total = 0; $total += $_ for @_; $total; }

O podrías usar la función de sum de List::Util :

use List::Util qw(sum); sub avg { @_ ? sum(@_) / @_ : 0 }

Usar shift no es tan común, excepto para extraer $self en Perl orientado a objetos. Pero como siempre llama a sus funciones como foo( ... ) , no importa si foo shift s o no shift la matriz de argumentos.
(Lo único que vale la pena mencionar acerca de una función es si asigna elementos a @_ , ya que estos son alias para las variables que proporcionó como argumentos. Asignar elementos a @_ suele ser malo).

Incluso si no puede cambiar la implementación del total , es seguro llamar al subcomité con una lista de argumentos explícita, ya que la lista de argumentos es una copia de la matriz:

(a) &total : llama al total con @_ idéntico, y anula los prototipos.
(b) total(@_) : total llamadas con una copia de @_ .
(c) &total(@_) : llama al total con una copia de @_ y anula los prototipos.

La forma (b) es estándar. La forma (c) no se debe ver, excepto en muy pocos casos para las subs en el mismo paquete donde la sub tiene un prototipo (y no usa prototipos), y tienen que ser anuladas por alguna razón oscura. Un testamento al mal diseño. La forma (a) solo es sensible para las llamadas de cola ( @_ = (...); goto &foo ) u otras formas de optimización (y la optimización prematura es la raíz de todo mal ).


Encontré la siguiente gema en http://perldoc.perl.org/perlsub.html :

"Sí, todavía hay problemas sin resolver que tienen que ver con la visibilidad de @_. Estoy ignorando esa pregunta por el momento. (Pero tenga en cuenta que si hacemos @_ de ámbito léxico, esas subrutinas anónimas pueden actuar como cierres ... ( Vaya, ¿esto suena un poco Lispish? (No importa.))) "

Es posible que haya encontrado uno de esos problemas :-(

OTOH amon probablemente tiene razón -> +1


He intentado un ejemplo simple:

#!/usr/bin/perl use strict; sub total { my $sum = 0; while(@_) { $sum = $sum + shift; } return $sum; } sub total1 { my ($a, $aa, $aaa) = @_; return ($a + $aa + $aaa); } my $s; $s = total(10, 20, 30); print $s; $s = total1(10, 20, 30); print "/n$s";

Ambas declaraciones impresas dieron respuesta como 60.

Pero personalmente lo siento, los argumentos deberían ser aceptados de esta manera:

my (arguments, @garb) = @_;

Con el fin de evitar cualquier tipo de problema posterior.


Perl es un poco demasiado laxo a veces y tener múltiples formas de acceder a los parámetros de entrada puede hacer que el código sea maloliente e inconsistente. A falta de una mejor respuesta, trate de imponer su propio estándar.

Aquí hay algunas maneras que he usado y visto

Sospechoso

sub login { my $user = shift; my $passphrase = shift; # Validate authentication return 0; }

Expandiendo @_

sub login { my ($user, $passphrase) = @_; # Validate authentication return 0; }

Indexación explícita

sub login { my user = $_[0]; my user = $_[1]; # Validate authentication return 0; }

Hacer cumplir los parámetros con prototipos de funciones ( sin embargo, esto no es popular )

sub login($$) { my ($user, $passphrase) = @_; # Validate authentication return 0; }

Lamentablemente, todavía tiene que realizar su propia validación de entrada enmarañada / comprobación de corrupción, es decir:

return unless defined $user; return unless defined $passphrase;

O mejor aún, un poco más informativo.

unless (defined($user) && defined($passphrase)) { carp "Input error: user or passphrase not defined"; return -1; }

Perldoc perlsub debería ser tu primer puerto de escala.

¡Espero que esto ayude!


Refs:

sub refWay{ my ($refToArray,$secondParam,$thirdParam) = @_; #work here } refWay(/@array, ''a'',''b'');

Hashway:

sub hashWay{ my $refToHash = shift; #(if pass ref to hash) #and i know, that: return undef unless exists $refToHash->{''user''}; return undef unless exists $refToHash->{''password''}; #or the same in loop: for (qw(user password etc)){ return undef unless exists $refToHash->{$_}; } } hashWay({''user''=>YourName, ''password''=>YourPassword});