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

Является ли "сдвиг" злом для обработки параметров подпрограммы Perl?

Я часто использую shift для распаковки параметров функции:

sub my_sub {
    my $self = shift;
    my $params = shift;
    ....
}

Однако многие из моих коллег проповедуют, что shift на самом деле злой. Не могли бы вы объяснить, почему я должен предпочесть

sub my_sub {
    my ($self, $params) = @_;
    ....
}

до shift?

4b9b3361

Ответ 1

Использование shift для распаковки аргументов не является злом. Это обычное соглашение и может быть самым быстрым способом обработки аргументов (в зависимости от того, сколько их есть и как они передаются). Вот один пример несколько общего сценария, в котором этот случай: простой аксессор.

use Benchmark qw(cmpthese);

sub Foo::x_shift { shift->{'a'} }
sub Foo::x_ref   { $_[0]->{'a'} }
sub Foo::x_copy  { my $s = $_[0]; $s->{'a'} }

our $o = bless {a => 123}, 'Foo';

cmpthese(-2, { x_shift => sub { $o->x_shift },
               x_ref   => sub { $o->x_ref   },
               x_copy  => sub { $o->x_copy  }, });

Результаты на perl 5.8.8 на моей машине:

            Rate  x_copy   x_ref x_shift
x_copy  772761/s      --    -12%    -19%
x_ref   877709/s     14%      --     -8%
x_shift 949792/s     23%      8%      --

Не драматично, но вот оно. Всегда проверяйте свой сценарий на своей версии perl на целевом оборудовании, чтобы узнать наверняка.

shift также полезен в тех случаях, когда вы хотите сместить invocant, а затем вызвать метод SUPER::, передав оставшийся @_ as-is.

sub my_method
{
  my $self = shift;
  ...
  return $self->SUPER::my_method(@_);
}

Если бы у меня была очень длинная серия операций my $foo = shift; в верхней части функции, я мог бы вместо этого использовать массовую копию из @_. Но в общем, если у вас есть функция или метод, который принимает более чем несколько аргументов, с помощью именованных параметров (то есть, ловя все @_ в %args хэш или ожидает один хэш ссылочный аргумент) гораздо лучше подход.

Ответ 2

Это не зло, это что-то вкусное. Вы часто увидите стили, используемые вместе:

sub new {
    my $class = shift;
    my %self = @_;
    return bless \%self, $class;
}

Я использую shift, когда есть один аргумент, или когда я хочу рассматривать первые несколько аргументов иначе, чем остальные.

Ответ 3

Это, как говорили другие, дело вкуса.

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

sub foo {
    my $foo = shift;       # a thing of some sort.
    my $bar = shift;       # a horse of a different color.
    my $baz = shift || 23; # a pale horse.

    # blah
}

Если вы действительно обеспокоены скоростью вызова ваших подпрограмм, не распаковывайте свои аргументы вообще - обращайтесь к ним напрямую, используя @_. Будьте осторожны, это ссылки на данные вызывающего абонента, с которыми вы работаете. Это обычная идиома в ПОЭ. POE предоставляет кучу констант, которые вы используете для получения позиционных параметров по имени:

sub some_poe_state_handler {
    $_[HEAP]{some_data} = 'chicken';
    $_[KERNEL]->yield('next_state');
}

Теперь большая глупая ошибка, которую вы можете получить, если вы обычно распаковываете параметры со сдвигом:

sub foo {
    my $foo = shift;
    my $bar = shift;
    my @baz = shift;

    # I should really stop coding and go to bed.  I am making dumb errors.

}

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

Если мои коллеги сказали, что есть большая проблема с использованием перехода на распаковку, я бы попросил показать, почему это плохо. Если дело твердое, то я кое-что узнаю. Если это фальшивка, я мог бы опровергнуть ее и помочь остановить распространение анти-знаний. Тогда я бы посоветовал нам определить стандартный метод и следовать ему для будущего кода. Возможно, я даже попытаюсь настроить политику Perl:: Critic для проверки принятого стандарта.

Ответ 4

Присвоение @_ списку может принести некоторые дополнительные дополнительные функции.

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

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

например.

sub do_something {
 my ($foo) = @_;
}

позже отредактирован в

sub do_something {
  my ($foo,$bar) = @_; # still works
}

однако

sub do_another_thing {
  my $foo = shift;
}

Если другой коллега, который догматически использует первую форму (возможно, они считают сдвиг злом) редактирует ваш файл и рассеянно обновляет его, чтобы читать

sub do_another_thing {
  my ($foo, $bar)  = shift; # still 'works', but $bar not defined
}

и они, возможно, ввели тонкую ошибку.

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

например.

 my (%arguments) = (user=>'defaultuser',password=>'password',@_);

Я бы все же считал это вопросом стиля/вкуса. Я думаю, что самое главное - применить один стиль или другой с последовательностью, подчиняясь принципу наименьшего удивления.

Ответ 5

Я не думаю, что shift - зло. Использование shift показывает вашу готовность фактически называть переменные - вместо использования $_[0].

Лично я использую shift, когда есть только один параметр для функции. Если у меня есть несколько параметров, я буду использовать контекст списка.

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

 my $otherResult = encode($otherString, $format);

 sub encode {
   my ($string,$format) = @_;
   ...
 }

Ответ 6

Perl:: Критик - ваш друг здесь. Это следует за "стандартами", установленными в книге Damian Conway Perl Best Practices. Запуск с помощью --verbose 11 дает вам объяснение, почему все плохо. Не распаковывать @_ сначала на ваших подсайтах - это серьезность 4 (из 5). Например:

echo 'package foo; use warnings; use strict; sub quux { my foo= shift; my (bar,baz) = @_;};1;' | perlcritic -4 --verbose 11

Always unpack @_ first at line 1, near 'sub quux { my foo= shift; my (bar,baz) = @_;}'.
  Subroutines::RequireArgUnpacking (Severity: 4)
    Subroutines that use `@_' directly instead of unpacking the arguments to
    local variables first have two major problems. First, they are very hard
    to read. If you're going to refer to your variables by number instead of
    by name, you may as well be writing assembler code! Second, `@_'
    contains aliases to the original variables! If you modify the contents
    of a `@_' entry, then you are modifying the variable outside of your
    subroutine. For example:

       sub print_local_var_plus_one {
           my ($var) = @_;
           print ++$var;
       }
       sub print_var_plus_one {
           print ++$_[0];
       }

       my $x = 2;
       print_local_var_plus_one($x); # prints "3", $x is still 2
       print_var_plus_one($x);       # prints "3", $x is now 3 !
       print $x;                     # prints "3"

    This is spooky action-at-a-distance and is very hard to debug if it's
    not intentional and well-documented (like `chop' or `chomp').

    An exception is made for the usual delegation idiom
    `$object->SUPER::something( @_ )'. Only `SUPER::' and `NEXT::' are
    recognized (though this is configurable) and the argument list for the
    delegate must consist only of `( @_ )'.

Ответ 7

Существует оптимизация для назначения списка.

Единственная ссылка, которую я мог найти, этот.

5.10.0 непреднамеренно отключена оптимизация, которая вызвала измеримое снижение производительности в списке назначение, например, часто используется для назначьте параметры функции из @_. Оптимизация была восстановлена, и исправлена ​​регрессия производительности.

Это пример затронутой регрессии производительности.

sub example{
  my($arg1,$arg2,$arg3) = @_;
}

Ответ 8

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