В perl, является ли неправильной практикой вызывать несколько подпрограмм с аргументами по умолчанию?
Я изучаю perl и понимаю, что общепринятой практикой является распаковка аргументов подпрограммы с помощью shift. Я также понимаю, что обычной и приемлемой практикой является опускание аргументов функции для использования массива @_
по умолчанию.
Учитывая эти две вещи, если вы вызываете подпрограмму без аргументов, @_
может (и будет, если использовать shift) быть изменен. Означает ли это, что вызов другой подпрограммы с аргументами по умолчанию или, по сути, с помощью массива @_
после этого считается плохой практикой? Рассмотрим этот пример:
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
}
Чувство моего чувства говорит мне, что использование shift для распаковки аргументов фактически будет плохой практикой, если ваша подпрограмма фактически не должна изменять переданные аргументы, но я читал в нескольких местах, включая Stack Overflow, что это не плохо практика.
Итак, возникает вопрос: если использование сдвига является обычной практикой, должен ли я всегда считать, что переданный список аргументов может быть изменен как побочный эффект подпрограммы (например, подпрограмма &total
в цитированном примере)? Может быть, способ передать аргументы по значению, поэтому я могу быть уверен, что список аргументов не изменяется, поэтому я мог бы использовать его снова (например, в подпрограмме &avg
в цитируемом тексте)?
Ответы
Ответ 1
В общем случае, shift
из аргументов в порядке, использование &
sigil для вызова функций не является. (За исключением некоторых очень конкретных ситуаций, которые вы, вероятно, никогда не встретите.)
Ваш код может быть переписан, так что total
не shift
из @_
. Использование цикла for может быть даже более эффективным.
sub total {
my $total = 0;
$total += $_ for @_;
$total;
}
Или вы можете использовать функцию sum
от List::Util
:
use List::Util qw(sum);
sub avg { @_ ? sum(@_) / @_ : 0 }
Использование shift
не является общим, за исключением извлечения $self
в объектно-ориентированном Perl. Но поскольку вы всегда называете свои функции типа foo( ... )
, не имеет значения, если foo
shift
или не shift
массив аргументов.
(Единственное, что стоит отметить о функции, это то, присваивается ли она элементам в @_
, поскольку они являются псевдонимами для переменных, которые вы указали как аргументы. Назначение элементам в @_
обычно плохо.)
Даже если вы не можете изменить реализацию total
, вызов sub с явным списком аргументов безопасен, так как список аргументов является копией массива:
(a) &total
- вызывает total
с идентичным @_
и переопределяет прототипы.
(b) total(@_)
- вызывает total
с копией @_
.
(c) &total(@_)
- вызывает total
с копией @_
и переопределяет прототипы.
Форма (b) является стандартной. Форма (c) не должна быть видна, за исключением очень немногих случаев для подсетей внутри одного и того же пакета, где у суб-прототипа (и не используются прототипы), и они должны быть переопределены по какой-то неясной причине. Свидетельство плохого дизайна.
Форма (а) имеет смысл только для хвостовых вызовов (@_ = (...); goto &foo
) или других форм оптимизации (и преждевременная оптимизация - корень всего зла).
Ответ 2
Вам следует избегать использования стиля вызова &func;
, если у вас нет действительно веской причины, и полагайте, что другие делают то же самое.
Чтобы защитить ваш @_
от изменения вызываемым пользователем, просто выполните &func()
или func
.
Ответ 3
Perl иногда немного слабее, и наличие нескольких способов доступа к входным параметрам может привести к вонючим и непоследовательным кодам. Из-за отсутствия лучшего ответа попытайтесь навязать свой собственный стандарт.
Вот несколько способов, которые я использовал и видел
Увертливый
sub login
{
my $user = shift;
my $passphrase = shift;
# Validate authentication
return 0;
}
Расширение @_
sub login
{
my ($user, $passphrase) = @_;
# Validate authentication
return 0;
}
Явное индексирование
sub login
{
my user = $_[0];
my user = $_[1];
# Validate authentication
return 0;
}
Использовать параметры с прототипами функций (это не популярно)
sub login($$)
{
my ($user, $passphrase) = @_;
# Validate authentication
return 0;
}
К сожалению, вам все равно придется выполнить собственную проверку на наличие ошибок /taint, т.е.
return unless defined $user;
return unless defined $passphrase;
или еще лучше, немного более информативный
unless (defined($user) && defined($passphrase)) {
carp "Input error: user or passphrase not defined";
return -1;
}
Perldoc perlsub действительно должен быть вашим первым портом захода.
Надеюсь, это поможет!
Ответ 4
Вот несколько примеров, где важно тщательное использование @_
.
1. Hash-y Аргументы
Иногда вы хотите написать функцию, которая может принимать список пар ключ-значение, но один из них является наиболее распространенным, и вы хотите, чтобы это было доступно без необходимости ключа. Например
sub get_temp {
my $location = @_ % 2 ? shift : undef;
my %options = @_;
$location ||= $options{location};
...
}
Итак, теперь, если вы вызываете функцию с нечетным числом аргументов, первое - это местоположение. Это позволяет get_temp('Chicago')
или get_temp('New York', unit => 'C')
или даже get_temp( unit => 'K', location => 'Nome, Ak')
. Это может быть более удобным API для ваших пользователей. Перемещая нечетный аргумент, теперь @_
является четным списком и может быть присвоен хешу.
2. Диспетчерская
Допустим, у нас есть класс, который мы хотим, чтобы отправлять методы по имени (возможно, AUTOLOAD может быть полезным, мы будем рулон). Возможно, это командная строка script, где аргументы - это методы. В этом случае мы определяем два метода отправки: один "чистый" и один "грязный". Если мы будем звонить с флагом -c
, мы получим чистый. Эти методы находят метод по имени и называют его. Разница в том, как. Загрязненный оставляет себя в трассе стека, чистый должен быть больше кливера, но отправляет, не попадая в трассировку стека. Мы делаем метод death
, который дает нам этот след.
#!/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);
}
Итак, теперь, если мы вызываем ./test.pl
для вызова метода смерти
$ ./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
но мы видим dispatch_dirty
в следе. Если вместо этого мы вызываем ./test.pl -c
, то теперь мы используем чистый диспетчер и получаем
$ ./test.pl -c death Adios
Robot: Adios at ./test.pl line 33
Unusual::death('Unusual=HASH(0x9427188)', 'Adios') called at ./test.pl line 44
Ключевым здесь является goto
(а не злая goto), которая берет ссылку подпрограммы и сразу же переключает исполнение на эту ссылку, используя текущий @_
. Вот почему я должен unshift @_, $self
, чтобы invocant был готов к новому методу.
Ответ 5
работ:
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});
Ответ 6
Я попробовал простой пример:
#!/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";
Оба оператора печати дали ответ как 60.
Но лично я чувствую, что аргументы должны приниматься таким образом:
my (arguments, @garb) = @_;
чтобы избежать какой-либо проблемы последней.
Ответ 7
Я нашел следующий камень в http://perldoc.perl.org/perlsub.html:
"Да, есть еще нерешенные проблемы, связанные с видимостью @_. Я игнорирую этот вопрос на данный момент. (Но обратите внимание, что если мы сделаем @_ лексически ограниченным, эти анонимные подпрограммы могут действовать как закрытие... (Джи, это звучит немного Lispish? (Ничего.)))"
Возможно, вы столкнулись с одной из следующих проблем: - (
OTOH amon, вероятно, прав → +1