Является ли 'shift' злом для обработки параметров подпрограммы Perl? - PullRequest
27 голосов
/ 25 августа 2009

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

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

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

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

до shift?

Ответы [ 8 ]

50 голосов
/ 25 августа 2009

Использование 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 также полезно в тех случаях, когда вы хотите сместить инвоканта и затем вызвать метод SUPER::, передав оставшиеся @_ как есть.

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

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

20 голосов
/ 25 августа 2009

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

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

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

11 голосов
/ 25 августа 2009

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

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

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. POE предоставляет набор констант, которые вы используете для получения позиционных параметров по имени:

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

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

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 для проверки на принятый стандарт.

8 голосов
/ 25 августа 2009

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

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

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

, например

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',@_);

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

1 голос
/ 25 августа 2009

Есть оптимизация для назначения списка.

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

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

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

sub example{
  my($arg1,$arg2,$arg3) = @_;
}
1 голос
/ 25 августа 2009

Perl :: Критик твой друг здесь. Это соответствует «стандартам», установленным в книге Дамиана Конвея «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 `( @_ )'.
1 голос
/ 25 августа 2009

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

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

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

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

 sub encode {
   my ($string,$format) = @_;
   ...
 }
0 голосов
/ 25 августа 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...