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


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


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.


@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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.