Скалярное значение зависит от толчка или нет ... (Раку)

12

Мне трудно понять, когда и почему значение, сохраняемое помещаемым Scalarконтейнером, изменяется после отправки. Я попытаюсь проиллюстрировать проблему, с которой я столкнулся, в более сложном контексте на двух стилизованных примерах.

* Пример 1 * В первом примере скаляр $iпомещается в массив @bкак часть a List. После отправки значение, хранящееся в скаляре, явно обновляется в последующих итерациях цикла for с использованием $i++инструкции. Эти обновления влияют на значение в массиве @b: в конце цикла for @b[0;0]равно 3и больше не равно 2.

my @b;
my $i=0;
for 1..3 -> $x {
  $i++;
  say 'Loose var $i: ', $i.VAR.WHICH, " ", $i.VAR.WHERE;
  if $x == 2 {
     @b.push(($i,1));
     say 'Pushed $i   : ', @b[0;0].VAR.WHICH, " ", @b[0;0].VAR.WHERE;
  }
}
say "Post for-loop";
say "Array       : ", @b;
say 'Pushed $i   : ', @b[0;0].VAR.WHICH, " ", @b[0;0].VAR.WHERE;

Выходной пример 1:

Loose var $i: Scalar|94884317665520 139900170768608
Loose var $i: Scalar|94884317665520 139900170768648
Pushed $i   : Scalar|94884317665520 139900170768648
Loose var $i: Scalar|94884317665520 139900170768688
Post for-loop
Array       : [(3 1)]
Pushed $i   : Scalar|94884317665520 139900170768688

* Пример 2 * Во втором примере скаляр $iявляется переменной цикла. Несмотря на то, $iобновляется после того , как была нажата (теперь неявно , а не в явном виде), значение $iв массиве @cникак не изменится после толчка; то есть после цикла for это все еще 2не 3.

my @c;
for 1..3 -> $i {
  say 'Loose var $i: ', $i.VAR.WHICH, " ", $i.VAR.WHERE;
  if $i == 2 {
     @c.push(($i,1));
     say 'Pushed $i   : ', @c[0;0].VAR.WHICH, " ", @c[0;0].VAR.WHERE;
  }
}
say "Post for-loop";
say "Array       : ", @c;
say 'Pushed $i   : ', @c[0;0].VAR.WHICH, " ", @c[0;0].VAR.WHERE;;

Выходной пример 2:

Loose var $i: Scalar|94289037186864 139683885277408
Loose var $i: Scalar|94289037186864 139683885277448
Pushed $i   : Scalar|94289037186864 139683885277448
Loose var $i: Scalar|94289037186864 139683885277488
Post for-loop
Array       : [(2 1)]
Pushed $i   : Scalar|94289037186864 139683885277448

Вопрос: Почему $iв @bв примере 1 , обновленный после толчка, в то время как $iв @cв примере 2 не является?

edit : После комментария @ timotimo я включил вывод .WHEREв примерах. Это показывает (WHICH / логическое) скалярное тождество $iостается тем же самым, в то время как его адрес памяти изменяется через различные итерации цикла. Но это не объясняет, почему в примере 2 выдвинутый скаляр остается привязанным к той же WHICH-идентичности в сочетании со старым адресом («448»).

Ozzy
источник
2
я могу дать вам ответ на вопрос, почему КОГДА, кажется, остается прежним; посмотрите на реализацию: github.com/rakudo/rakudo/blob/master/src/core.c/Scalar.pm6#L8 - это зависит только от используемого дескриптора, который представляет собой небольшой объект, который содержит такие вещи, как имя переменная и ограничение типа. если вы используете .WHEREвместо, .WHICHвы можете видеть, что скаляр на самом деле каждый раз повторяет цикл. Это происходит потому, что заостренные блоки «вызываются», а подпись «привязывается» к каждому вызову.
Тимотимо
@raiph Во время цикла Пример 1 показывает тот же шаблон, что и Пример 2: оба имеют изменяющиеся адреса, о которых сообщает .WHERE, что говорит, я согласен. Но само по себе это не объясняет, почему Пример 2 имеет другое окончание, чем Пример 1.
ozzy

Ответы:

5

Скалярное значение - это просто контейнер. Вы можете думать о них как об умном указателе, а не как о примитивном значении.

Если вы делаете назначение

$foo = "something"; #or
$bar++;

Вы меняете значение скаляров, контейнер остается прежним.

Рассматривать

my @b; 
my $i=0; 
for 1..5 -> $x { 
  $i++; 
  @b.push(($i<>,1)); # decontainerize $i and use the bare value
} 
say @b;

а также

my @b; 
my $i=0; 
for 1..5 -> $x { 
  $i := $i + 1;  # replaces the container with value / change value
  @b.push(($i,1)); 
} 
say @b;

Оба из которых работают, как ожидалось. Но: в обоих случаях вещь в списке больше не является изменчивой, потому что нет контейнера.

@b[4;0] = 99; 

поэтому умрет. Так что просто используйте переменную цикла, верно?

Нет.

for 1..5 -> $x { 
  @b.push(($x,1)); # 
} 
@b[4;0] = 99; #dies

даже если мы перебираем список изменчивых вещей.

my $one = 1;
my $two = 2;
my $three = 3;
my $four = 4;
my $five = 5;

for ($one, $two, $three, $four, $five) -> $x { 
  @b.push(($x,1)); 
} 
@b[4;0] = 99; #dies

Таким образом, здесь нет псевдонимов, вместо этого переменная цикла всегда является одним и тем же контейнером и получает назначенные значения, которые поступают из других контейнеров.

Мы можем сделать это все же.

for ($one, $two, $three, $four, $five) <-> $x { 
  @b.push(($x,1)); 
} 
@b[4;0] = 99; # works

for ($one, $two, $three, $four, $five) -> $x is rw { 
  @b.push(($x,1)); 
} 
@b[4;0] = 99; # works too

Способ сделать изменяемую вещь - это использовать промежуточную переменную.

for 1..5 -> $x { 
  my $j = $x;
  @b.push(($j,1)); # a new container 
} 
@b[4;0] = 99;

работает отлично. Или короче и больше в оригинальном контексте

my @b; 
my $i=0; 
for 1..5 -> $x { 
  $i++; 
  @b.push((my $ = $i, 1)); # a new anonymous container
} 
@b[4;0] = 99;
say @b; # [(1 1) (2 1) (3 1) (4 1) (99 1)]

Смотрите также:

https://perl6advent.wordpress.com/2017/12/02/#theoneandonly https://docs.perl6.org/language/containers

Holli
источник
1
Вместо того ($x,1), вы можете также сделать [$x,1]который бы создать новый контейнер (также 1, кстати)
Элизабет Mattijsen
@ElizabethMattijsen Но тогда это массив, который делает "подъем" да?
Холли
Не уверен, что вы подразумеваете под «поднятием», но если вы контейнеризуете значения при создании, тогда да.
Элизабет Маттийсен
@ Холли Спасибо за ваш ответ. Я не уверен, если это решает вопрос, хотя. Ваш ответ сфокусирован на изменчивости контейнера, которую, я думаю, я понимаю. Что я не понимаю, так это то, почему в первом примере толкаемый контейнер $ i - или лучше: его значение - обновляется после нажатия, в то время как во втором примере значение толкаемого контейнера остается статически привязанным к значению на данный момент толчка. Первый пример имеет некоторый смысл для меня (контейнер - указатель на Intобъект -> Intзаменяется в цикле for -> контейнер указывает на новый Int), но второй - нет.
Оззи
@ Холли, я постараюсь прояснить вопрос.
Оззи
3

Поработав и подумав над моим вышеупомянутым вопросом в течение некоторого времени, я поставлю на вопрос ... Это чистая догадка с моей стороны, поэтому, пожалуйста, не стесняйтесь говорить, что это бессмысленно, и если вы знаете, Почему...

В первом примере $iопределяется за пределами лексической области цикла for. Следовательно, $iсуществует независимо от цикла и его итераций. Когда на $iнего ссылаются из цикла, есть только один, $iкоторый может быть затронут. Это то, $iчто вставляется в него @b, и его содержимое впоследствии изменяется в цикле.

Во втором примере $iопределяется внутри лексической области действия цикла for. Как указал @timotimo, указанный блок get вызывается для каждой итерации, как подпрограмма; $iпоэтому вновь объявляется для каждой итерации и попадает в соответствующий блок. Когда на $iнего ссылаются внутри цикла, ссылка на специфичную для итерации блока $i, которая обычно перестает существовать, когда заканчивается соответствующая итерация цикла. Но поскольку в какой-то момент $iпроисходит принудительное переключение, сборщик мусора не может удалить @cссылку на специфическое для итерации $iзначение 2после завершения итерации. Он останется в существовании ..., но все равно будет отличаться от $iпоследующих итераций.

Ozzy
источник
@raiph Спасибо. Я это сделаю. Возможно, что кто-то с большей проницательностью, чем я, может (пере) сформулировать ответ правильно. В любом случае, я не приму мой собственный ответ как правильный, пока он не будет подтвержден (или улучшен) теми, кто знает (а не догадывается, как я).
Оззи