Мне не хватает семантики $_ здесь?

Во время профилирования я наткнулся на эту функцию в List:: UtilsBy:

sub rev_nsort_by(&@) {
    my $keygen = shift;
    my @keys = map { local $_ = $_[$_]; scalar $keygen->( $_ ) } 0 .. $#_;
    return map { $_[$_] } sort { $keys[$b] <=> $keys[$a] } 0 .. $#_;
}

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

my @objects = load_objects_from_database();
# sort by rating, highest first
@objects = rev_nsort_by { $_->rating } @objects;

Я прекрасно понимаю, почему rev_nsort_by, как показано выше, работает по назначению, но мне интересно, почему это так сложно. В частности, мне интересно, почему

my @keys = map { local $_ = $_[$_]; scalar $keygen->( $_ ) } 0 .. $#_;

не был записан как

my @keys = map { scalar $keygen->( $_ ) } @_;

который выглядит функционально эквивалентным мне. Я пропустил какое-то поведение в области углов в случае $_ здесь, которое более длинная версия имеет значение в некотором роде?

Ответ 1

Здесь присутствует случай с тонким краем: внутри foreach циклов или выражений map переменная по умолчанию $_ имеет псевдоним с исходным значением. Например.

@nums = 1..5;
@foo = map { $_ *= 2 } @nums;
# both @foo and @nums contain 2, 4, 6, 8, 10 now.

Однако константы не являются допустимыми lvalues, поэтому мы не могли этого сделать, например

@foo = map { $_ *= 2 } 1, 2, 3, 4, 5;
# Modification of read-only value

Массив @_ также сглаживается с исходными значениями, поэтому представьте себе следующие случаи кросс:

sub buggy (&@) { my $cb = shift; map $cb->($_), @_ };

buggy { $_ *= 2 } 1, 2, 3;   # Modification of read-only value attempted
buggy { $_[0] *= 2} 1, 2, 3; # ditto

my @array = 1 .. 5;
buggy { $_ *= 2 } @array;    # @array now is 2, 4, 6, 8, 10
buggy { $_[0] *= 2 } @array; # ditto

Псевдонимы являются транзитивными, поэтому внутренний $_[0] псевдоним $_, который сглажен внешнему $_[0], который является псевдонимом для константы 1/$array[0].

Итак, что делает local $_ = $_[$_] здесь?

  • Он делает копию значения, тем самым избегая этого безумного поведения псевдонимов
  • Он показывает намерение сделать $_ видимым для обратного вызова.

Обеспечение семантики копирования (таким образом, избегая неожиданных побочных эффектов) кажется естественным для Perl, поэтому эта функция хорошо разработана и не особенно перегружена.

(Примечание: map {local $_ = $_; ...} @_ было бы достаточно, чтобы сделать копию)