Где $ _ изменяется в этом Perl-коде? - PullRequest
3 голосов
/ 05 мая 2011

Следующий Perl-код генерирует предупреждение в PerlCritic (от Activestate):

sub natural_sort {
    my @sorted;
    @sorted = grep {s/(^|\D)0+(\d)/$1$2/g,1} sort grep {s/(\d+)/sprintf"%06.6d",$1/ge,1} @_;
}

Сгенерированное предупреждение:

Не изменять $ _ в функциях списка

Подробнее об этом предупреждении здесь

Я не понимаю предупреждение, потому что не думаю, что я изменяю $ _, хотя я полагаю, что ядолжно быть.Может кто-нибудь объяснить мне, пожалуйста?

Ответы [ 5 ]

10 голосов
/ 05 мая 2011

Оба ваших grep изменяют $_, потому что вы используете s//. Например, это:

grep {s/(^|\D)0+(\d)/$1$2/g,1}

такой же, как этот:

grep { $_ =~ s/(^|\D)0+(\d)/$1$2/g; 1 }

Я думаю, вам лучше использовать map, поскольку вы ничего не фильтруете с помощью grep s, вы просто используете grep в качестве итератора:

sub natural_sort {
    my $t;
    return map { ($t = $_) =~ s/(^|\D)0+(\d)/$1$2/g; $t }
           sort
           map { ($t = $_) =~ s/(\d+)/sprintf"%06.6d",$1/ge; $t }
           @_;
}

Это должно делать то же самое и молчать критика. Возможно, вы захотите взглянуть на List::MoreUtils, если вам нужны операторы списка лучше, чем обычный map.

3 голосов
/ 05 мая 2011

Этот и другие случаи описаны в perldoc perlvar :

Вот места, где Perl будет примите $ _, даже если вы его не используете:

  • Следующие функции:

abs, будильник, chomp, chop, chr, chroot, cos, определено, eval, exp, glob, hex, int, lc, lcfirst, length, log, lstat, mkdir, oct, ord, pos, print, quotemeta, readlink, readpipe, ref, требуют, обратный (в скалярном контексте только), rmdir, sin, split (на своем второй аргумент), sqrt, stat, study, uc, ucfirst, unlink, unpack.

  • Все тесты файлов (-f, -d), кроме -t, который по умолчанию равен STDIN. Смотри -X

  • Операции сопоставления с образцом m //, s /// и tr /// (иначе y ///), когда используется без оператора = ~.

  • Переменная итератора по умолчанию в цикле foreach, если нет другой переменной в комплект поставки.

  • Неявная переменная итератора в функциях grep () и map ().

  • Неявная переменная предоставляется ().

  • Место по умолчанию для ввода входной записи, когда результат операции протестирован как единственный
    критерий времени теста. Снаружи
    во время теста этого не произойдет.

3 голосов
/ 05 мая 2011

Вы делаете подстановку (т. Е. s///) в grep, которая изменяет $_, т. Е. Списываемый список.

2 голосов
/ 05 мая 2011

Многие люди правильно ответили, что оператор s изменяет $_, однако в скором выпуске Perl 5.14.0 появится новый флаг r для оператора s (т. Е. * 1005). *) который вместо того, чтобы модифицировать на месте, вернет измененные элементы. Узнайте больше на Эффективный Perler . Вы можете использовать perlbrew для установки этой новой версии.

Редактировать: Perl 5.14 теперь доступен! Объявление Объявление Дельта

Вот функция, предложенная mu (с использованием map), но с использованием этой функции:

use 5.14.0;

sub natural_sort {
    return map { s/(^|\D)0+(\d)/$1$2/gr }
           sort
           map { s/(\d+)/sprintf"%06.6d",$1/gre }
           @_;
}
1 голос
/ 05 мая 2011

ОЧЕНЬ важная часть, которую пропустили другие ответы, заключается в том, что строка

grep {s/(\d+)/sprintf"%06.6d",$1/ge,1} @_;

фактически изменяет аргументы, передаваемые в функцию, а не их копии.

grepявляется командой фильтрации, а значение в $_ внутри блока кода является псевдонимом одного из значений в @_.@_ в свою очередь содержит псевдонимы аргументов, передаваемых функции, поэтому, когда оператор s/// выполняет свою подстановку, выполняется изменение исходного аргумента.Это показано в следующем примере:

sub test {grep {s/a/b/g; 1} @_}

my @array = qw(cat bat sat);

my @new = test @array;

say "@new";   # prints "cbt bbt sbt" as it should
say "@array"; # prints "cbt bbt sbt" as well, which is probably an error

Требуемое поведение (применить функцию, которая изменяет $_ к копии списка) было инкапсулировано как функция apply вколичество модулей.Мой модуль List :: Gen содержит такую ​​реализацию.apply также довольно просто написать самому:

sub apply (&@) {
    my ($sub, @ret) = @_;
    $sub->() for @ret;
    wantarray ? @ret : pop @ret
}

С этим ваш код может быть переписан как:

sub natural_sort {
    apply {s/(^|\D)0+(\d)/$1$2/g} sort apply {s/(\d+)/sprintf"%06.6d",$1/ge} @_
}

Если ваша цель с повторными заменами состоит в том, чтобы выполнитьПри сортировке исходных данных с примененной переходной модификацией, вам следует рассмотреть идиому Perl, известную как преобразование Шварца , которая является более эффективным способом достижения этой цели.

...