Почему работает козел-оператор?
Разница между массивами и списками, а также между списком и скалярным контекстом обсуждалась в сообществе Perl совсем немного в прошлом году (и каждый год, действительно). Я прочитал статьи из chromatic и friedo, а также этот рекомендуется монахам node. Я сейчас пытаюсь понять оператора козла, задокументированного в perlsecret.
Вот какой код я использовал для изучения:
# right side gets scalar context, so commas return rightmost item
$string = qw(stuff junk things);
say $string; # things
# right side gets list context, so middle is list assigned in scalar context
$string = () = qw(stuff junk things);
say $string; # 3
# right side gets list context, so creates a list, assigns an item to $string2, and
# evaluates list in scalar context to assign to $string
$string = ($string2) = qw(stuff junk things);
say $string; # 3
say $string2; # stuff
Я думаю, что я достаточно далеко, чтобы понять всю работу списка и скалярного контекста. Оператор запятой в скалярном контексте возвращает свою правую сторону, поэтому первый пример просто присваивает последнему элементу в выражении запятой (без каких-либо запятых) значение $string
. В других примерах назначение выражения для запятой в список помещает его в контекст списка, поэтому создается список, а списки, оцененные в скалярном контексте, возвращают свой размер.
Есть две части, которые я не понимаю.
Во-первых, списки должны быть неизменными. Это подчеркивается неоднократно friedo. Я полагаю, что назначение через =
из списка в список распределяет назначения из элементов в одном списке на элементы в другом списке, поэтому во втором примере $string2
получает 'stuff'
и почему мы можем распаковать @_
через список. Однако я не понимаю, как возможно работать с ()
, пустым списком. С учетом моего нынешнего понимания, поскольку списки являются неизменными, размер списка останется 0, а затем присвоение размера $stuff
в примерах 2 и 3 даст ему значение 0. Листинги не являются фактически неизменяемыми?
Во-вторых, я читал много раз, что списки фактически не существуют в скалярном контексте. Но объяснение оператора goatse заключается в том, что это назначение списка в скалярном контексте. Не является ли это контр-примером для оператора, который не существует в скалярном контексте? Или что-то еще происходит здесь?
Обновление. Поняв ответ, я думаю, что дополнительная пара круглых скобок помогает концептуализировать, как это работает:
$string = ( () = qw(stuff junk things) );
Внутри parens =
- это присвоение "агрегату", а также оператор присваивания списка (который отличается от оператора скалярного присваивания и который не следует путать с "контекстом списка"; и скалярное присваивание может происходить в любом списке или скалярном контексте). ()
никоим образом не изменяется. =
имеет возвращаемое значение в Perl, а результат назначения списка присваивается $string
через левый =
. Присвоение $string
дает скалярный контекст для RHS (все в parens), а в скалярном контексте возвращаемое значение оператора назначения списка - это количество элементов в RHS.
Вместо этого вы можете назначить назначение списка RHS в контекст списка:
($string) = ( () = qw(stuff junk things) );
В соответствии с perlop назначение списка в контексте списка возвращает список назначенных lvalues, который здесь пуст, поскольку ничего не назначено до ()
. Итак, здесь $string будет undef
.
Ответы
Ответ 1
Это помогает запомнить, что в Perl присваивание является выражением и что вы должны думать о значении выражения (значение оператора присваивания), а не о "значении списка".
Значение выражения qw(a b)
составляет ('a', 'b')
в контексте списка и 'b'
в скалярном контексте, но значение выражения (() = qw(a b))
составляет ()
в контексте списка и 2
в скалярном контексте. Значения (@a = qw(a b))
следуют одной и той же схеме. Это связано с тем, что pp_aassign
, оператор присваивания списка, решает вернуть счет в скалярном контексте:
else if (gimme == G_SCALAR) {
dTARGET;
SP = firstrelem;
SETi(lastrelem - firstrelem + 1);
}
(pp_hot.c строка 1257, номера строк могут быть изменены, но ближе к концу PP(pp_aassign)
.)
Тогда, кроме значения оператора присваивания, это побочный эффект оператора присваивания. Побочным результатом назначения списка является копирование значений с правой стороны на левую сторону. Если правая часть сначала заканчивается, остальные элементы левой стороны получают undef
; если в левой части сначала заканчиваются значения, остальные элементы правой стороны не копируются. Когда LHS имеет значение ()
, назначение списка ничего не копирует. Но значение самого присваивания по-прежнему является количеством элементов в RHS, как показано фрагментом кода.
Ответ 2
Вы неправильно понимаете. Списки, оцененные в скалярном контексте, не получают их размер. Фактически, практически невозможно иметь список в скалярном контексте. Здесь у вас есть скалярное назначение с двумя операндами, скалярная переменная слева и назначение списка справа (заданный скалярный контекст с помощью скалярного присваивания). Назначение списков в скалярном контексте оценивает количество элементов справа от присваивания.
Итак, в:
1 $foo
2 =
3 ()
4 =
5 ('bar')
2, скалярное присваивание, дает 1 и 4 скалярных контекста.
4, назначение списка, дает 3 и 5 контекста списка, но тем не менее сам по себе в скалярном контексте и возвращает его соответствующим образом.
(Когда = - назначение списка или скалярное назначение определяется исключительно из окружающего синтаксиса, если левый операнд является хэшем, массивом, хэш-срезом, срезом массива или в круглых скобках, это назначение списка, иначе оно является скалярным назначением.)
Эта обработка присвоений списков в скалярном контексте делает возможным следующий код:
while ( my ($key, $value) = each %hash ) {
где list-context - это итератор, который возвращает (в контексте списка) один ключ и значение для каждого вызова и пустой список, когда это делается, давая while 0 и завершая цикл.
Ответ 3
Во-первых, "список" - это неоднозначный термин. Даже вопрос использует его для обозначения двух разных вещей. Я подозреваю, что вы, возможно, делаете это, не осознавая этого, и что это значительная часть причины вашего замешательства.
Я буду использовать "значение списка" для обозначения того, что оператор возвращает в контексте списка. Напротив, "оператор списка" относится к оператору EXPR,EXPR,EXPR,...
также известному как "оператор запятой" [1].
Во-вторых, вы должны прочитать Scalar vs List Assignment Operator.
Я предполагаю, что назначение через = из списка в список распределяет назначения из элементов в одном списке по элементам в другом списке, поэтому во втором примере $ string2 получает "вещи", и поэтому мы можем распаковать @_ посредством назначения списка.
Правильный.
Я много раз читал, что списки на самом деле не существуют в скалярном контексте.
Эта формулировка очень неоднозначна. Вы, кажется, говорите о значениях списка (которые находятся в памяти), но скалярный контекст существует только в коде (где находятся операторы).
- Оператор списка/запятой может оцениваться в скалярном контексте.
- Значение списка не может быть возвращено в скалярном контексте.
Скалярный контекст - это контекст, в котором оператор может быть оценен.
Оператор, вычисленный в скалярном контексте, не может вернуть список. Должен вернуть скаляр. Грубо говоря, вы могли бы сказать, что список не может быть возвращен в скалярном контексте.
С другой стороны, оператор списка/запятой может оцениваться в скалярном контексте. например, scalar(4,5,6)
. Каждый оператор может быть оценен в любом контексте (хотя это не обязательно полезно для этого).
Но объяснение оператора goatse состоит в том, что это присвоение списка в скалярном контексте.
Включает один, да.
Значения списка и операторы присваивания списка - это две разные вещи. Одно значение. Другой - это кусок кода.
Оператор присваивания списка, как и любой другой оператор, может оцениваться в скалярном контексте. Оператор присваивания списка в скалярном контексте возвращает количество скаляров, возвращаемых его RHS.
Поэтому, если вы оцениваете () = qw(abc)
в скалярном контексте, он вернет три, так как qw()
поместил три скаляра в стек.
Тем не менее, я не понимаю, как присвоение(), пустой список, может работать.
Точно так же, как присваивание ($x,$y) = qw(stuff junk things)
игнорирует третий элемент, возвращаемый RHS, () = qw(stuff junk things)
игнорирует все элементы, возвращаемые RHS.
В моем нынешнем понимании, поскольку списки неизменны, размер списка останется 0
Сказать "размер списка останется нулевым" для ()=qw(abc)
- все равно, что сказать "значение скаляра останется 4" для 4+5
.
Для начала, есть вопрос, о каком списке вы говорите. LHS вернул один, RHS вернул один, и оператор присваивания может вернуть один.
Значение списка, возвращаемое LHS, будет иметь длину 0.
Значение списка, возвращаемое RHS, будет иметь длину 3.
В скалярном контексте оператор присваивания списка возвращает количество скаляров, возвращаемых RHS (3).
В контексте списка оператор присваивания списка возвращает скаляры, возвращаемые LHS, как lvalues (пустой список).
списки должны быть неизменными.
Если вы думаете с точки зрения изменчивости списка, вы где-то ошиблись. [2]
Заметки:
-
Документы вызывают EXPR,EXPR,EXPR,...
два экземпляра бинарного оператора, но его легче понять как один N-арный оператор, и он фактически реализован как один N-арный оператор. Даже в скалярном контексте.
-
На самом деле это не так, но давайте не будем идти дальше по этому неправильному пути.