Холли принял ответ, который процитировал два комментария Брэда. Я сознательно сохранил это вкратце.
Но вы читаете это. 1 Так что я буду go в обратном направлении с этим ответом.
($bar,...)
это список, содержащий $bar
, а не $bar
значение
Поведение, показанное в вопросе, действительно касается контейнеров и значений и того, как литералы списка хранят контейнеры, а не их значения 1 :
my $bar = 0;
my $list = ($bar,);
say $list[0]; # 0
$bar = 1;
say $list[0]; # 1
Лень против нетерпеливого аспекта - это просто вещи, которые делают то, что они все должны делать. Подчеркивая этот первый пункт может быть достаточно, чтобы вдохновить вас сосредоточиться на контейнерах против ценностей. И, возможно, это поможет вам быстро понять, что пошло не так в коде Холли. Но, возможно, нет. 1 Так что я продолжу.
Что происходит в ленивом случае?
Ленивый список ждет, пока не будет запрошено значение прежде чем пытаться произвести это. Затем он просто выполняет необходимую работу и делает паузу, давая контроль, пока его не попросят произвести другое значение в более позднее время.
В коде Холли требования for
l oop значения.
При первом обращении к for
l oop требуется значение из выражения lazy
. Это оборачивается и требует значения из выражения gather
'd. Последний затем вычисляет до take
, к этому времени он создает список, первым элементом которого является контейнер $bar
. Этот список является результатом take
.
Затем .print
печатает этот первый список. На момент печати $bar
все еще содержит 0
. (Первое увеличение $bar
еще не произошло.)
Во второй раз вокруг for
l oop внутренняя структура управления, включающая take
(loop
), имеет вид вновь вошел. Первое, что происходит, это то, что $bar
увеличивается впервые. Затем проверяется (и не выполняется) условие выхода l oop, поэтому начинается второй раз вокруг l oop. Еще один список создан. Тогда это take
d.
Когда печатается второй список, его первый элемент, который является контейнером $bar
, печатается как 1
, а не 0
, потому что в этот момент, будучи увеличенный, $bar
теперь содержит 1
.
(Если Холли написал код, который удерживал первый список, и напечатал этот первый список снова сейчас , только что напечатав второй список, они обнаружили бы, что первый список также теперь печатается с 1
, больше не 0
. Потому что все take
d списки имеют такой же $bar
контейнер как их первый элемент.)
И также третий список.
После того, как третий список был напечатан, for
l oop требует четвертого go при gather
. Это снова вводит loop
в операторе после оператора take
. $bar
увеличивается в третий раз до 3
, а затем срабатывает условие last if $bar > 2;
, выходящее из l oop (и, таким образом, выражение является gather
'd и, в конечном итоге, весь оператор .print for ...
) .
Что происходит в нетерпеливом случае?
Все выполнение gather
завершено до любого ввода print
.
В конце этого, конструкция for
имеет последовательность из трех списков. Он еще не звонил .print
. В третий раз вокруг loop
в gather
осталось $bar
, содержащее 3
.
Далее, .print
вызывается в каждом из трех списков. $bar
содержит 3
, поэтому все они печатаются с 3
в качестве первого элемента.
Решение проблемы путем переключения на массив
Я думаю, что идиоматический c способ решения с этим можно переключиться с литерала списка на литерал массива:
[$bar, @bar[$bar]]
# instead of
($bar, @bar[$bar])
Это работает, потому что, в отличие от литерала list , литерал массива обрабатывает элемент, который является контейнером как r-значение 2 , т.е. он копирует значение , содержащееся в контейнере из этого контейнера, вместо хранения самого контейнера.
Просто так получается, что значение копируется в другой новый Scalar
контейнер. (Это потому, что все элементы новых несобственных массивов являются fre sh Scalar
контейнерами; это одна из главных вещей, которая отличает массив от списка.) Но эффект в этом контексте так же, как если бы значение было скопировано непосредственно в массив, потому что больше не имеет значения, что значение, содержащееся в $bar
, изменяется по мере продолжения.
В результате получается, что первый элемент из трех массивов в итоге содержат, соответственно, 0
, 1
и 2
, три значения, которые содержались в $bar
на момент создания каждого массива.
Решение проблемы переключившись на выражение
Как заметил Холли, написание $bar + 0
также сработало.
Фактически подойдет любое выражение, если оно не просто $bar
сам по себе.
Конечно, выражение должно работать и возвращать правильное значение. Я думаю, что $bar.self
должен работать и возвращать правильное значение независимо от того, какое значение $bar
связано или назначено.
(Хотя это действительно немного странно читается; $bar.self
это не $bar
само по себе, если $bar
привязано к контейнеру Scalar
! Действительно, в еще более нелогичном повороте, даже $bar.VAR
, который использует .VAR
, метод, который "возвращает базовый элемент Scalar
объект, если таковой имеется. " все равно заканчивается обработкой r-значением ! * *
Нужно ли обновлять do c?
Вышеприведенное является вполне логичным следствием:
Если do c слабый, то, вероятно, в его объяснении одного из последних двух аспектов. Похоже, что это в первую очередь литеральный список.
Синтаксис do c page содержит раздел о различных литералах, включая литералы Array, но не список литералов. do c * Списки, последовательности и массивы имеет раздел Список литералов (а не один в массивах) но здесь не упоминается, что они делают с Scalar
с.
Предположительно, это заслуживает внимания.
Страница Списки, последовательности и массивы также содержит Ленивые списки раздел, который, возможно, может быть обновлен.
Собрав все вышеперечисленное, похоже, простейшим исправлением do c может быть обновление Lists, последовательности и массивы стр.
Сноски
1 В моей первой паре версий этого ответа ( 1 , 2 , я пытался заставить Холли задуматься о влиянии контейнеров на значения. Но это не помогло им и, возможно, не сработало и для вас. Если вы не знакомы с контейнерами Раку, подумайте о прочтении:
2 Некоторые детали обсуждения в Википедии «значений l и r» не соответствуют Раку, но общий принцип тот же.