Подтвердить что ты не робот

Есть ли разница между сдвигом Perl и назначением из @_ для параметров подпрограммы?

Не будем игнорировать на мгновение лучшую практику Damian Conway не более трех позиционных параметров для любой данной подпрограммы.

Есть ли разница между двумя приведенными ниже примерами в отношении производительности или функциональности?

Использование shift:

sub do_something_fantastical {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;
}

Использование @_:

sub do_something_fantastical {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
}

При условии, что оба примера одинаковы с точки зрения производительности и функциональности, что люди думают об одном формате над другим? Очевидно, что пример с использованием @_ - это меньше строк кода, но разве не более читаемо использовать shift, как показано в другом примере? Мнения с хорошими рассуждениями приветствуются.

4b9b3361

Ответ 1

Там функциональная разница. shift изменяет @_, а присвоение из @_ - нет. Если после этого вам не нужно использовать @_, эта разница, вероятно, не имеет значения для вас. Я стараюсь всегда использовать назначение списка, но иногда использую shift.

Однако, если я начну с shift, вот так:

 my( $param ) = shift;

Я часто создаю эту ошибку:

 my( $param, $other_param ) = shift;

Это потому, что я не часто использую shift, поэтому я забываю перейти к правой части задания, чтобы изменить его на @_. Это точка наилучшей практики при использовании shift. Я мог бы создавать отдельные строки для каждого shift, как это было в вашем примере, но это просто утомительно.

Ответ 2

По крайней мере, в моих системах это зависит от версии Perl и архитектуры:

#!/usr/bin/perl -w
use strict;
use warnings;
use autodie;

use Benchmark qw( cmpthese );

print "Using Perl $] under $^O\n\n";

cmpthese(
    -1,
    {
        shifted   => 'call( \&shifted )',
        list_copy => 'call( \&list_copy )',
    }
);

sub call {
    $_[0]->(1..6);  # Call our sub with six dummy args.
}

sub shifted {
    my $foo   = shift;
    my $bar   = shift;
    my $baz   = shift;
    my $qux   = shift;
    my $quux  = shift;
    my $corge = shift;

    return;
}

sub list_copy {
    my ($foo, $bar, $baz, $qux, $quux, $corge) = @_;
    return;
}

Результаты:

Using Perl 5.008008 under cygwin

              Rate   shifted list_copy
shifted   492062/s        --      -10%
list_copy 547589/s       11%        --


Using Perl 5.010000 under MSWin32

              Rate list_copy   shifted
list_copy 416767/s        --       -5%
shifted   436906/s        5%        --


Using Perl 5.008008 under MSWin32

              Rate   shifted list_copy
shifted   456435/s        --      -19%
list_copy 563106/s       23%        --

Using Perl 5.008008 under linux

              Rate   shifted list_copy
shifted   330830/s        --      -17%
list_copy 398222/s       20%        --

Итак, похоже, что list_copy обычно на 20% быстрее, чем смещение, за исключением Perl 5.10, где смещение на самом деле немного быстрее!

Обратите внимание, что это были быстро полученные результаты. Фактические разницы в скорости будут больше, чем указано здесь, так как Benchmark также подсчитывает время, необходимое для вызова и возврата подпрограмм, которые будут оказывать умеренное влияние на результаты. Я не проводил никаких исследований, чтобы увидеть, делает ли Perl какой-либо особый вид оптимизации. Ваш пробег может отличаться.

Пол

Ответ 3

Я бы предположил, что пример сдвига медленнее, чем использование @_, потому что он вызывает 6 функций вместо 1. Независимо от того, является ли это заметным или даже измеримым, это другой вопрос. Бросьте каждый в цикл из 10 k итераций и время их.

Что касается эстетики, я предпочитаю метод @_. Похоже, было бы слишком легко испортить порядок переменных, используя метод сдвига с случайным вырезанием и вставкой. Кроме того, я видел, как многие люди делают что-то вроде этого:

sub do_something {
   my $foo = shift;
   $foo .= ".1";

   my $baz = shift;
   $baz .= ".bak";

   my $bar = shift;
   $bar .= ".a";
}

Это, IMHO, очень неприятно и может легко привести к ошибкам, например. если вы вырезаете блок baz и вставьте его под панелью. Я все для определения переменных рядом с тем, где они используются, но я думаю, что определение переданных в переменных в верхней части функции имеет приоритет.

Ответ 4

Обычно я использую первую версию. Это связано с тем, что я обычно должен иметь проверку ошибок вместе со сдвигами, которые легче писать. Скажем,

sub do_something_fantastical {
    my $foo   = shift || die("must have foo");
    my $bar   = shift || 0;  # $bar is optional
    # ...
}

Ответ 5

Я предпочитаю распаковать @_ как список (ваш второй пример). Хотя, как и все в Perl, бывают случаи, когда использование shift может быть полезным. Например, методы passthru, которые должны быть переопределены в базовом классе, но вы хотите убедиться, что все еще работает, если они не переопределены.


package My::Base;
use Moose;
sub override_me { shift; return @_; }

Ответ 6

Я предпочитаю использовать

sub do_something_fantastical {
    my ( $foo, $bar, $baz, $qux, $quux, $corge ) = @_;
}

Потому что это более читаемо. Когда этот код не вызывается часто, это стоит того. В очень редких случаях вы хотите, чтобы функция вызывалась часто, а затем использовала @_ напрямую. Он эффективен только для очень коротких функций, и вы должны быть уверены, что эта функция не будет развиваться в будущем (функция Write once). В этом случае я оценил в 5.8.8, что для одного параметра сдвиг быстрее, чем $_ [0], но для двух параметров с использованием $_ [0] и $_ [1] выполняется быстрее, чем сдвиг, сдвиг.

sub fast1 { shift->call(@_) }

sub fast2 { $_[0]->call("a", $_[1]) }

Но вернемся к вашему вопросу. Я также предпочитаю @_ назначение в одной строке по сдвигам для многих параметров таким образом.

sub do_something_fantastical2 {
    my ( $foo, $bar, $baz, @rest ) = @_;
    ...
}

Когда Подозреваемый @rest не будет очень большим. В другом случае

sub raise {
    my $inc = shift;
    map {$_ + $inc} @_;
}

sub moreSpecial {
    my ($inc, $power) = (shift(), shift());
    map {($_ + $inc) ** $power} @_;
}

sub quadratic {
    my ($a, $b, $c) = splice @_, 0, 3;
    map {$a*$_*$_ + $b*$_ + $c} @_;
}

В редких случаях мне нужна оптимизация хвостовых вызовов (вручную), тогда я должен работать непосредственно с @_, чем для короткой функции.

sub _switch    #(type, treeNode, transform[, params, ...])
{
    my $type = shift;
    my ( $treeNode, $transform ) = @_;
    unless ( defined $type ) {
        require Data::Dumper;
        die "Broken node: " . Data::Dumper->Dump( $treeNode, ['treeNode'] );
    }
    goto &{ $transform->{$type} }   if exists $transform->{$type};
    goto &{ $transform->{unknown} } if exists $transform->{unknown};
    die "Unknown type $type";
}

sub switchExTree    #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, $treeNode->{type};    # set type
    goto &_switch;                    # tail call
}

sub switchCompact                     #(treeNode, transform[, params, ...])
{
    my $treeNode = $_[0];
    unshift @_, (%$treeNode)[0];      # set type given as first key
    goto &_switch;                    # tail call
}

sub ExTreeToCompact {
    my $tree = shift;
    return switchExTree( $tree, \%transformExTree2Compact );
}

sub CompactToExTree {
    my $tree = shift;
    return switchCompact( $tree, \%transformCompact2ExTree );
}

Где% transformExTree2Compact и% transformCompact2ExTree - хэши с типом в ключе и кодом ref в значении, которое может вызвать вызов switchExTree или switchCompact it selfs. Но этот подход редко нужен и должен держать менее стоящие пальцы в колледже.

В заключение, читаемость и ремонтопригодность должны быть особенно важны в perl, а назначение @_ в одной строке лучше. Если вы хотите установить значения по умолчанию, вы можете сделать это сразу после него.

Ответ 7

Лучший способ, IMHO, - это небольшая смесь двух, как в новой функции в модуле:

our $Class;    
sub new {
    my $Class = shift;
    my %opts = @_;
    my $self = \%opts;
    # validate %opts for correctness
    ...
    bless $self, $Class;
}

Затем все вызывающие аргументы для конструктора передаются как хэш, что делает код более читаемым, чем просто список параметров.

Плюс, как Брайан сказал, переменная @_ немодифицирована, что может быть полезно в необычных случаях.

Ответ 8

Я подозреваю, что вы выполняете (грубый) эквивалент:

push @bar, shift @_ for (1 :: $big_number);

Тогда вы делаете что-то неправильно. Я всегда использую форму my ($foo, $bar) = @_;, потому что я выстрелил себе в ногу, используя последнее несколько раз...

Ответ 9

Я предпочитаю

sub factorial{
  my($n) = @_;

  ...

}

По той простой причине, что Комодо сможет рассказать мне, что такое аргументы, когда я использую его.