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

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

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

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

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

to shift?


person Nikolai Prokoschenko    schedule 25.08.2009    source источник
comment
Я думаю, вам следует спросить своих коллег, почему они думают, что сдвиг - это зло, и поделиться с нами своими ответами здесь, в SO   -  person ccheneson    schedule 25.08.2009
comment
@Bombe, я вижу, что вы довольно часто используете PHP, но чувствуете необходимость оскорблять Perl. Надеюсь, вы знаете, что большинство распространенных жалоб на Perl в равной степени относится и к PHP. В каждом языке есть свои уродливые моменты, но у большинства есть что предложить, чтобы с ними справиться. Чтобы было ясно, как и Perl, PHP - отличный инструмент, к которому нам посчастливилось иметь свободный доступ - то же самое касается Java, Python, Ruby и так далее. Пожалуйста, воздержитесь от оскорбления выбора людьми инструментов. Смайлик не меняет характер вашего комментария. Оскорбление с улыбкой по-прежнему является оскорблением.   -  person daotoad    schedule 25.08.2009
comment
Как бы то ни было, Ресси, любой, кто догматично защищает одну особенность Perl над другой, упускает из виду суть Perl.   -  person Chris Lutz    schedule 26.08.2009


Ответы (8)


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

person John Siracusa    schedule 25.08.2009
comment
Вы должны показать, когда на самом деле бесполезная смена происходит быстрее. Я думаю, что с 2 или 3 аргументами быстрее скопировать @_. Как правило, я использую shift, когда есть 1 аргумент, и копирую @_, когда их больше 1. - person mpeters; 25.08.2009

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

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

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

person Chas. Owens    schedule 25.08.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.

}

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

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

person daotoad    schedule 25.08.2009
comment
+1, но учтите, что если аргументы нуждаются в комментариях (а их больше двух), лучше использовать именованные аргументы и предоставить хеш по умолчанию. - person Sinan Ünür; 25.08.2009
comment
Я обычно выставляю свой лимит на 3, но абсолютно. - person daotoad; 26.08.2009

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

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

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

e.g.

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
}

и они могли внести небольшую ошибку.

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

e.g.

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

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

person cms    schedule 25.08.2009
comment
Я вообще не думаю, что my ($foo, $bar) = shift; является тонкой ошибкой. Это кирпич в голову, не так ли? - person Telemachus; 25.08.2009
comment
Я видел это слишком много раз. Глупая ошибка, конечно, но на удивление легко стать «слепым по коду», когда вы много ее читаете. - person cms; 25.08.2009
comment
Обычно, если вы распаковываете по сдвигу, дополнительный параметр добавляется путем добавления еще одной строки. my $foo = shift; my $bar = shift; Я никогда не видел, что вы описываете - person daotoad; 25.08.2009
comment
Это просто мое предположение, почему кто-то может подумать, что смена передач - это «зло». Я бы сам не согласился с таким выводом. - person cms; 25.08.2009

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

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

 my $result = decode($theString);

 sub decode {
   my $string = shift;

   ...
 }

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

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

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

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

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

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

sub example{
  my($arg1,$arg2,$arg3) = @_;
}
person Brad Gilbert    schedule 25.08.2009
comment
Если у кого-то есть лучшая ссылка, пожалуйста, оставьте комментарий. - person Brad Gilbert; 25.08.2009

Perl :: Critic - ваш друг. Он соответствует стандартам, установленным в книге Дэмиана Конвея 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 `( @_ )'.
person olle    schedule 25.08.2009
comment
Похоже, это ошибочно приписывает случай использования @_ напрямую, вместо того, чтобы брать локальные копии. Это действительно почти всегда неправильно. Однако ни смещение, ни присвоение @_ списку не страдают от проблем, описанных в выходных данных. Тем не менее, я полагаю, что осмысление выводов этой утилиты может объяснить, почему люди считают такие операторы, как shift, злом. - person cms; 25.08.2009
comment
Не стоит слишком доверять Perl Best Practices. Слишком много ситуаций, чтобы применить одно правило ко всем. - person brian d foy; 26.08.2009

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

person Quentin    schedule 25.08.2009
comment
-1 за неверную информацию. Как говорит @John Siracusa, это обычная идиома Perl - использовать shift для обработки аргументов. - person Tony Miller; 25.08.2009
comment
Я не понимаю, где он утверждал, что это не обычная идиома Perl. Он сказал, что это было медленнее (что верно, потому что массивом аргументов нужно манипулировать, а не просто получать доступ). - person JoshJordan; 25.08.2009
comment
Тони вроде как прав. Основываясь на моем тестировании, сдвиг быстрее для 2 параметров или меньше. Назначение списка выполняется быстрее для 3 и более. Поскольку все, что принимает 3, 4 или 10 параметров, вероятно, не должно выполнять позиционные параметры таким образом, использование стиля сдвига является разумным. - person Adam Kennedy; 25.08.2009
comment
@JoshJordan: Во всех случаях это было бы медленнее по той причине, которую вы указали, но ... он был сильно оптимизирован в интерпретаторе perl, что привело к тому, что сдвиг одного аргумента стал быстрее, чем его копирование, как показывают тесты в Ответ Джона Сиракузы. (Однако назначение списков при захвате большого количества параметров еще быстрее.) - person Dave Sherohman; 25.08.2009