Valor escalar sendo afetado após push, ou não ... (Raku)

12

Tenho dificuldade em entender quando e por que o valor mantido por um Scalarcontêiner enviado é afetado após o envio. Tentarei ilustrar a questão que encontrei em um contexto mais complicado em dois exemplos estilizados.

* Exemplo 1 * No primeiro exemplo, um escalar $ié empurrado para uma matriz @bcomo parte de a List. Após o envio, o valor mantido pelo escalar é explicitamente atualizado nas iterações posteriores do loop for usando a $i++instrução Essas atualizações afetam o valor da matriz @b: no final do loop @b[0;0]for , é igual a 3, e não mais 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;

Exemplo de saída 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

* Exemplo 2 * No segundo exemplo, o escalar $ié a variável de loop. Mesmo que $ié atualizado depois de ter sido empurrado (agora implicitamente, em vez de explicitamente), o valor de $iem conjunto @cse não mudar após o impulso; ou seja, após o loop for 2, ainda é , não 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;;

Exemplo de saída 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

Pergunta: Porque é que $iem @bno exemplo 1 atualizado após o impulso, enquanto $iem @cno exemplo 2 não é?

edit : Após o comentário de @ timotimo, incluí a saída de .WHEREnos exemplos. Isso mostra a identidade escalar (WHICH / logic) de $ipermanece a mesma, enquanto seu endereço de memória é alterado pelas várias iterações de loop. Mas não explica por que, no exemplo 2, o escalar empurrado permanece vinculado à mesma identidade WHICH em combinação com um endereço antigo ("448).

ozzy
fonte
2
eu posso lhe dar a resposta para por que o QUE parece permanecer o mesmo; veja a implementação: github.com/rakudo/rakudo/blob/master/src/core.c/Scalar.pm6#L8 - depende apenas do descritor que está sendo usado, que é um pequeno objeto que contém coisas como o nome de a variável e a restrição de tipo. se você usar em .WHEREvez de, .WHICHpoderá ver que o escalar é realmente um objeto diferente a cada vez no loop. Isso acontece porque os blocos pontiagudos são "chamados" e a assinatura é "vinculada" a cada chamada.
timotimo 5/10/19
@raiph Durante o loop, o Exemplo 1 mostra o mesmo padrão do Exemplo 2: ambos têm endereços alterados relatados por .WHERE, o que é revelador, eu concordo. Mas, por si só não explica por que Exemplo 2 chega a um final diferente do que Exemplo 1.
ozzy

Respostas:

5

Um valor escalar é apenas um contêiner. Você pode pensar neles como uma espécie de ponteiro inteligente, e não como um valor primitivo.

Se você fizer uma tarefa

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

você está alterando o valor das escalares, o contêiner permanece o mesmo.

Considerar

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

e

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;

Ambos funcionam como esperado. Mas: nos dois casos, a coisa na lista não é mais mutável, porque não há contêiner.

@b[4;0] = 99; 

portanto, morrerá. Então, basta usar a variável loop, certo?

Não.

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

mesmo se iterarmos sobre uma lista de coisas mutáveis.

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

Portanto, não há aliasing aqui, em vez disso, a variável loop é sempre o mesmo contêiner e obtém valores atribuídos provenientes dos outros contêineres.

Nós podemos fazer isso embora.

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

Uma maneira de tornar "a coisa" mutável é usar uma variável intermediária.

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

funciona bem. Ou mais curto e mais no contexto original

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)]

Veja também:

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

Holli
fonte
11
Em vez de ($x,1), você também pode fazer [$x,1]o que iria criar um novo contêiner (também para 1, BTW)
Elizabeth Mattijsen
@ElizabethMattijsen Mas então é a Matriz que faz o "levantamento" sim?
Holli
Não sei ao certo o que você quer dizer com "levantamento", mas se você colocar em contêiner os valores na criação, então sim.
Elizabeth Mattijsen 5/10/19
@ Holli Obrigado pela sua resposta. Não tenho certeza se ele aborda a questão. Sua resposta se concentra na mutabilidade do contêiner, que acho que entendo. O que não entendo é por que, no primeiro exemplo, o contêiner enviado $ i - ou melhor: seu valor - é atualizado após o envio, enquanto no segundo exemplo o valor do contêiner enviado permanece estaticamente vinculado ao valor no momento do impulso. O primeiro exemplo faz algum sentido para mim (o container é um ponteiro para o Intobjeto -> Inté substituído no loop for - - o container aponta para novo Int), mas o segundo não.
Ozzy #
@ Holli Vou tentar esclarecer a questão.
Ozzy #
3

Depois de brincar e pensar na minha pergunta acima por algum tempo, aposto uma resposta ... É pura conjectura da minha parte, então fique à vontade para dizer que não faz sentido se é e se você sabe, porque...

No primeiro exemplo, $ié definido fora do escopo lexical do loop for. Conseqüentemente, $iexiste independentemente do loop e de suas iterações. Quando $ié referenciado de dentro do loop, há apenas um $ique pode ser afetado. É isso $ique é empurrado para dentro @b, e seu conteúdo é modificado posteriormente no loop.

No segundo exemplo, $ié definido dentro do escopo lexical do loop for. Como o @timotimo apontou, o bloco apontado é chamado para cada iteração, como uma sub-rotina; $ié, portanto, declarado recentemente para cada iteração e com escopo definido para o respectivo bloco. Quando $ié referenciada dentro do loop, a referência é específica ao bloco de iteração $i, que normalmente deixaria de existir quando a respectiva iteração de loop terminasse. Mas como em algum momento $ié enviado para @c, a referência ao $ivalor de retenção específico da iteração de bloco 2não pode ser excluída pelo coletor de lixo após o término da iteração. Ele permanecerá na existência ..., mas ainda será diferente das $iiterações posteriores.

ozzy
fonte
@raiph Obrigado. Eu farei isso. Talvez alguém com mais discernimento do que eu possa (re) formular a resposta corretamente. De qualquer forma, não aceitarei minha própria resposta como correta até que seja confirmada (ou melhorada) por quem sabe (em vez de adivinhar, como eu).
ozzy