Perl "оператор обратной запятой" (пример из книги "Программирование на Perl, 4-е издание")
Я читаю " Программирование на Perl " и натолкнулся на странный пример, который, кажется, не имеет смысла. Книга описывает, как оператор запятой в Perl будет возвращать только последний результат при использовании в скалярном контексте.
Пример:
# After this statement, $stuff = "three"
$stuff = ("one", "two", "three");
Затем в книге приводится пример "оператора обратной запятой" несколькими страницами позже (стр. 82).
# A "reverse comma operator".
return (pop(@foo), pop(@foo))[0];
Однако мне кажется, что это не совсем наоборот..?
Пример:
# After this statement, $stuff = "three"
$stuff = reverse_comma("one", "two", "three");
# Implementation of the "reverse comma operator"
sub reverse_comma {
return (pop(@_), pop(@_))[0];
}
Как это в любом случае противоположно нормальному оператору запятой? Результаты одинаковы, не обращены!
Вот ссылка на точную страницу. Пример в нижней части.
4 ответа
Это плохой пример, и о нем следует забыть.
То, что он демонстрирует, просто:
Обычно, если у вас есть последовательность выражений, разделенных запятыми в скалярном контексте, это можно интерпретировать как экземпляр оператора запятой, который оценивает до последней вещи в последовательности.
Однако, если вы положите эту последовательность в скобках и придерживаться
[0]
в конце она превращает эту последовательность в список и получает первый элемент, напримерmy $x = (1, 2, 3)[0];
По какой-то причине книга называет это "оператором обратной запятой". Это неправильное название; это просто список, в котором взят первый элемент.
Книга еще больше сбивает с толку, используя pop
функция дважды в аргументах. Они оцениваются слева направо, поэтому первый pop
оценивает "three"
а второй "two"
,
В любом случае: никогда не используйте операторы запятой или "обратная запятая" в реальном коде. Оба, вероятно, смущают будущих читателей.
Это милый и умный пример, но слишком серьезное размышление об этом отвлекает от его цели. В этом разделе книги показаны фрагменты списка. Все, что не входит в список, независимо от того, что находится в списке, не относится к цели примеров.
Вы только на странице 82 очень большой книги (мы буквально не могли вместить больше страниц, потому что мы были на пределе метода переплета), так что мы не могли бы бросить на вас. Среди других примеров фрагментов списка есть этот умный, который я бы не использовал в реальном коде. Это проклятие надуманных примеров.
Но скажем, там был оператор обратной запятой. Пришлось бы оценить обе стороны запятой. Многие ответы идут прямо для "просто вернуть первое". Это не особенность, хотя. Вы должны посетить каждое выражение, даже если вы сохраняете одно из них.
Рассмотрим эту гораздо более продвинутую версию с серией анонимных подпрограмм, которые я немедленно разыменую, каждая из которых печатает что-то, а затем возвращает результат:
use v5.10;
my $scalar = (
sub { say "First"; 35 } -> (),
sub { say "wantarray is ", 0+wantarray } -> (),
sub { say "Second"; 27 } -> (),
sub { say "Third"; 137 } -> ()
);
Парень есть только для приоритета, так как оператор присваивания связывается более тесно, чем оператор запятой. Здесь нет списка, хотя похоже, что он есть.
Вывод показывает, что Perl оценил каждый из них, хотя он сохранил последний:
First
wantarray is 0
Second
Third
Scalar is [137]
Плохо названная встроенная функция wantarray возвращает false, отмечая, что подпрограмма считает, что она находится в скалярном контексте.
Теперь предположим, что вы хотите перевернуть это, чтобы оно все равно оценивало каждое выражение, но оставляло первое. Вы можете использовать буквальный список доступа:
my $scalar = (
sub { say "First"; 35 } -> (),
sub { say "wantarray is ", 0+wantarray } -> (),
sub { say "Second"; 27 } -> (),
sub { say "Third"; 137 } -> ()
)[0];
С добавлением подписки, правая часть теперь является списком, и я вытаскиваю первый элемент. Обратите внимание, что вторая подпрограмма думает, что она сейчас находится в контексте списка. Я получаю результат первой подпрограммы:
First
wantarray is 1
Second
Third
Scalar is [35]
Но давайте поместим это в подпрограмму. Мне все еще нужно вызывать каждую подпрограмму, хотя я не буду использовать результаты других:
my $scalar = reverse_comma(
sub { say "First"; 35 },
sub { say "wantarray is ", 0+wantarray },
sub { say "Second"; 27 },
sub { say "Third"; 137 }
);
say "Scalar is [$scalar]";
sub reverse_comma { ( map { $_->() } @_ )[0] }
Или я бы использовал результаты других? Что делать, если я сделал что-то немного другое. Я добавлю побочный эффект от установки $last
к вычисляемому выражению:
use v5.10;
my $last;
my $scalar = reverse_comma(
sub { say "First"; 35 },
sub { say "wantarray is ", 0+wantarray },
sub { say "Second"; 27 },
sub { say "Third"; 137 }
);
say "Scalar is [$scalar]";
say "Last is [$last]";
sub reverse_comma { ( map { $last = $_->() } @_ )[0] }
Теперь я вижу функцию, которая делает скалярную запятую интересной. Он оценивает все выражения, и некоторые из них могут иметь побочные эффекты:
First
wantarray is 0
Second
Third
Scalar is [35]
Last is [137]
Не секрет, что tchrist удобен в написании сценариев оболочки. Конвертер Perl в csh был в основном его почтовым ящиком (или comp.lang.perl). В его примерах вы увидите какую-то оболочку, похожую на идиомы. Что-то вроде этого трюка, чтобы поменять местами два числовых значения одним оператором:
use v5.10;
my $x = 17;
my $y = 137;
say "x => $x, y => $y";
$x = (
$x = $x + $y,
$y = $x - $y,
$x - $y,
);
say "x => $x, y => $y";
Побочные эффекты важны там.
Итак, вернемся к Camel, у нас есть пример того, где побочным эффектом является оператор массива pop:
use v5.10;
my @foo = qw( g h j k );
say "list: @foo";
my $scalar = sub { return ( pop(@foo), pop(@foo) )[0] }->();
say "list: @foo";
Это показывает, что каждое выражение на любой стороне всех запятых оценивается. Я добавил оболочку подпрограммы, так как мы не показали полный пример:
list: g h j k
list: g h
Но ничто из этого не было тем разделом, который индексировал в литерал списка. Суть этого примера состояла не в том, чтобы вернуть результат, отличный от других примеров или возможностей Perl. Это должно было вернуть то же самое, предполагая, что оператор запятой действовал по-другому. Раздел посвящен фрагментам списка и демонстрирует фрагменты списка, поэтому материал в списке не был важной частью.
Давайте сделаем примеры более похожими.
Оператор запятой:
$scalar = ("one", "two", "three");
# $scalar now contains "three"
Оператор обратной запятой:
$scalar = ("one", "two", "three")[0];
# $scalar now contains "one"
Это обратная запятая, потому что $scalar
получает результат первого выражения, где обычный оператор запятой дает последнее выражение. (Если вы знаете что-нибудь о Лиспе, это как разница между progn
а также prog1
.)
Реализация "оператора обратной запятой" в качестве подпрограммы будет выглядеть примерно так:
sub reverse_comma {
return shift @_;
}
Обычная запятая будет оценивать свои операнды и затем возвращает значение правого операнда
my $v = $a, $b
наборы $v
к стоимости $b
В целях демонстрации фрагментов списка Camel предлагает некоторый код, который ведет себя как оператор запятой, но вместо этого оценивает его операнды и затем возвращает значение левого операнда
Нечто подобное можно сделать с помощью фрагмента списка, например
my $v = ($a, $b)[0]
который устанавливает $v
к стоимости $a
Вот и все, что нужно сделать. Книга не пытается предположить, что должна быть подпрограмма с обратной запятой, она просто рассматривает проблему вычисления двух выражений по порядку и возврата первого. Порядок оценки имеет значение только тогда, когда два выражения имеют побочные эффекты, поэтому в примере в книге используются pop
, который изменяет массив, а также возвращает значение
Мнимая проблема заключается в следующем
Предположим, я хочу, чтобы подпрограмма удалила два последних элемента массива, а затем вернула значение того, что раньше было последним элементом.
Обычно для этого требуется временная переменная, как это
my $last = pop @foo;
pop @foo;
return $last;
Но в качестве примера фрагмента списка код предполагает, что это также будет работать
# A "reverse comma operator".
return (pop(@foo), pop(@foo))[0];
Пожалуйста, поймите, что это не рекомендация. Есть несколько способов сделать это. Другой способ с одним утверждением
return scalar splice @foo, -2;
но это не использует срез списка, который является темой этого раздела книги. На самом деле я сомневаюсь, что авторы книги предложат что-то кроме простого решения с временной переменной. Это просто пример того, что может сделать срез списка
Надеюсь, это поможет