Почему работает козел-оператор?

Разница между массивами и списками, а также между списком и скалярным контекстом обсуждалась в сообществе 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]


Заметки:

  1. Документы вызывают EXPR,EXPR,EXPR,... два экземпляра бинарного оператора, но его легче понять как один N-арный оператор, и он фактически реализован как один N-арный оператор. Даже в скалярном контексте.

  2. На самом деле это не так, но давайте не будем идти дальше по этому неправильному пути.