В perl, является ли неправильной практикой вызывать несколько подпрограмм с аргументами по умолчанию? - программирование
Подтвердить что ты не робот

В 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 в цитируемом тексте)?

4b9b3361

Ответ 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