в 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 для распаковки аргументов на самом деле было бы плохой практикой, если только ваша подпрограмма не должна изменять переданные аргументы, но я читал в нескольких местах, включая переполнение стека, что это неплохая практика.

Таким образом, возникает вопрос: если использование сдвига является обычной практикой, всегда ли я должен предполагать, что переданный список аргументов может быть изменен в качестве побочного эффекта подпрограммы (например, подпрограммы &total в приведенном примере)? Может быть, есть способ передать аргументы по значению, чтобы я мог быть уверен, что список аргументов не изменится, чтобы я мог использовать его снова (как в подпрограмме &avg в цитируемом тексте)?


person Mythoranium    schedule 31.12.2012    source источник
comment
Кстати, я бы использовал return undef; вместо return; для avg без аргументов (как и вы для total). avg - функция, которая должна возвращать скаляр. Если он вернет меньше одного значения, это вызовет сюрпризы. например Следующее приведет к ошибке с пустым @data: { avg => avg(@data), ts => time() }   -  person ikegami    schedule 31.12.2012


Ответы (7)


В общем, shift использование аргументов - это нормально - использование символа & для вызова функций - нет. (За исключением некоторых очень специфических ситуаций, с которыми вы, вероятно, никогда не столкнетесь.)

Ваш код можно переписать, чтобы 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 shifts или не shift массив аргументов.
(Единственное, что стоит отметить о функции заключается в том, присваивает ли она элементы в @_, поскольку это псевдонимы для переменных, которые вы указали в качестве аргументов. Присвоение элементам в @_ обычно плохо.)

Даже если вы не можете изменить реализацию total, вызов подпрограммы с явным списком аргументов безопасен, поскольку список аргументов является копией массива:

(a) &total - вызывает total с идентичным @_ и отменяет прототипы.
(b) total(@_) - вызывает total с копией @_.
(c) &total(@_) - вызывает total с копией @_ и отменяет прототипы.

Форма (б) стандартная. Форму (c) не следует видеть, за исключением очень немногих случаев для подпрограмм внутри того же пакета, где подпрограмма имеет прототип (и не использует прототипы), и они должны быть переопределены по какой-то неясной причине. Свидетельство плохого дизайна. Форма (a) только подходит для хвостовых вызовов (@_ = (...); goto &foo) или других форм оптимизации (а преждевременная оптимизация - корень всех зол).

person amon    schedule 31.12.2012
comment
Спасибо, это именно тот ответ, который я искал. Фактически, я сам наткнулся на это, когда перечитывал главу «Изучение Perl» (6-е изд.) О подпрограммах. Книга на самом деле поощряет вас использовать амперсанд каждый раз, когда это не запрещено, но также указывает в сноске, что вызов подпрограммы с амперсандом, без каких-либо аргументов И без скобок, заставляет ее унаследовать @_. Я не читал заранее эту сноску и наткнулся именно на этот случай. - person Mythoranium; 31.12.2012
comment
goto &foo и &foo - это не одно и то же. Первый используется для управления стеком (например, чтобы сделать AUTOLOAD прозрачным). Последнее время от времени является незначительным удобством (например, sub warn { unshift @_, 'warn'; &log }). - person ikegami; 31.12.2012
comment
Есть сильные противники &, но их главный аргумент состоит в том, что вы можете случайно опустить скобки и получить распространение @ _-, когда оно вам не нужно (и фактор уродства). Я работал над кодовыми базами, в которых все функции, не относящиеся к CPAN, вызываются с помощью &; это не так уж и плохо, когда ты к этому привык. Как и все остальное, последовательность часто важнее того, в чем вы последовательны. - person ysth; 31.12.2012

Вам следует избегать использования стиля звонков &func;, если у вас нет действительно веской причины, и верить, что другие поступают так же.

Чтобы защитить свой @_ от изменений вызываемым пользователем, просто выполните &func() или func.

person ysth    schedule 31.12.2012

Perl иногда бывает слишком слабым, и наличие нескольких способов доступа к входным параметрам может привести к появлению вонючего и непоследовательного кода. Из-за отсутствия лучшего ответа попробуйте навязать свой собственный стандарт.

Вот несколько способов, которые я использовал и видел

Shifty

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;
}

К сожалению, вам все еще нужно выполнить свою собственную запутанную проверку ввода / проверку на помутнение, то есть:

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 действительно должен стать вашим первым портом захода.

Надеюсь это поможет!

person Matt Melton    schedule 31.12.2012
comment
Спасибо за подробный ответ, но он на самом деле не отвечает на мой вопрос, возможно, потому, что мой вопрос мог быть неправильно понят. Хотя я знаю, что существует несколько способов распаковки аргументов, разница в том, что некоторые из них (например, ($user, $passphrase) = @_;) не изменяют исходный список аргументов, а некоторые (например, изменчивый способ) меняют его. Поскольку я могу использовать подпрограммы, написанные не мной, я не знаю, могу ли я вызывать другую подпрограмму с той же переменной аргумента @_ несколько раз без необходимости вручную сохранять ее в другом массиве для дальнейшего использования. Я обновил вопрос. - person Mythoranium; 31.12.2012

Вот несколько примеров, когда важно осторожное использование @_.

1. Хешированные аргументы

Иногда вы хотите написать функцию, которая может принимать список пар ключ-значение, но одна из них - наиболее распространенное использование, и вы хотите, чтобы она была доступна без ключа. Например

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 может быть полезен, мы будем делать это вручную). Возможно, это сценарий командной строки, где аргументы - это методы. В этом случае мы определяем два метода отправки: один "чистый" и один "грязный". Если мы вызываем с флагом -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, чтобы вызывающий был готов для нового метода.

person Joel Berger    schedule 31.12.2012

Ссылки:

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});
person gaussblurinc    schedule 31.12.2012

Я пробовал простой пример:

#!/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";

Оба оператора print дали ответ как 60.

Но лично я считаю, что аргументы следует принимать следующим образом:

 my (arguments, @garb) = @_;

во избежание каких-либо проблем.

person Krishnachandra Sharma    schedule 31.12.2012

Я нашел следующий драгоценный камень в http://perldoc.perl.org/perlsub.html:

"Да, все еще остаются нерешенные проблемы, связанные с видимостью @_. Я пока игнорирую этот вопрос. (Но обратите внимание, что если мы сделаем @_ лексически ограниченным, эти анонимные подпрограммы могут действовать как замыкания ... ( Ну и дела, это звучит немного по-лиспийски? (Неважно.))) "

Вы могли столкнуться с одной из этих проблем :-(

OTOH amon, наверное, прав -> +1

person edgar.holleis    schedule 31.12.2012