La valeur scalaire est affectée après la poussée, ou non… (Raku)

12

J'ai du mal à comprendre quand et pourquoi la valeur détenue par un Scalarconteneur poussé est affectée après la poussée. Je vais essayer d'illustrer le problème que j'ai rencontré dans un contexte plus compliqué dans deux exemples stylisés.

* Exemple 1 * Dans le premier exemple, un scalaire $iest poussé sur un tableau @bdans le cadre de a List. Après le push, la valeur détenue par le scalaire est explicitement mise à jour dans les itérations ultérieures de la boucle for à l'aide de l' $i++instruction. Ces mises à jour ont un effet sur la valeur du tableau @b: à la fin de la boucle for, @b[0;0]est égal à 3et non plus 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;

Exemple de sortie 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

* Exemple 2 * Dans le deuxième exemple, le scalaire $iest la variable de boucle. Même si $iest mis à jour après avoir été poussé (maintenant implicitement plutôt qu'explicitement), la valeur de $iin array @cne change pas après la poussée; c'est-à-dire après la boucle for, ce n'est toujours 2pas le cas 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;;

Exemple de sortie 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

Question: Pourquoi $idans @bl'exemple 1 mis à jour après la poussée, alors que $idans @cl'exemple 2 n'est pas?

edit : Suite au commentaire de @ timotimo, j'ai inclus la sortie de .WHEREdans les exemples. Cela montre que l'identité scalaire (WHICH / logique) $ireste la même, tandis que son adresse mémoire change à travers les différentes itérations de boucle. Mais cela n'explique pas pourquoi dans l'exemple 2 le scalaire poussé reste lié à la même identité QUI en combinaison avec une ancienne adresse ("448).

ozzy
la source
2
je peux vous donner la réponse à pourquoi le QUI semble rester le même; regardez l'implémentation: github.com/rakudo/rakudo/blob/master/src/core.c/Scalar.pm6#L8 - cela ne dépend que du descripteur utilisé, qui est un petit objet qui contient des choses comme le nom de la variable et la contrainte de type. si vous utilisez à la .WHEREplace de .WHICHvous pouvez voir que le scalaire est en fait un objet différent à chaque fois autour de la boucle. Cela se produit car les blocs pointus sont "appelés" et la signature est "liée" à chaque appel.
timotimo
@raiph Pendant la boucle, l'exemple 1 montre le même modèle que l'exemple 2: les deux ont des adresses changeantes signalées par .WHERE, ce qui est révélateur, je suis d'accord. Mais en soi, cela n'explique pas pourquoi l'exemple 2 arrive à une fin différente de l'exemple 1.
ozzy

Réponses:

5

Une valeur scalaire n'est qu'un conteneur. Vous pouvez les considérer comme une sorte de pointeur intelligent, plutôt que comme une valeur primitive.

Si vous faites une mission

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

vous modifiez la valeur scalaire, le conteneur reste le même.

Considérer

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

et

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;

Les deux fonctionnent comme prévu. Mais: Dans les deux cas, la chose dans la liste n'est plus modifiable, car il n'y a pas de conteneur.

@b[4;0] = 99; 

mourra donc. Alors, utilisez simplement la variable de boucle, non?

Non.

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

même si nous parcourons une liste de choses mutables.

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

Il n'y a donc pas d'aliasing ici, à la place la variable de boucle est toujours le même conteneur et obtient des valeurs attribuées provenant des autres conteneurs.

Nous pouvons cependant le faire.

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

Une façon de rendre "la chose" mutable est d'utiliser une variable intermédiaire.

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

fonctionne bien. Ou plus court et plus dans le contexte d'origine

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

Voir également:

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

Holli
la source
1
Au lieu de ($x,1), vous pouvez aussi faire ce [$x,1]qui pourrait créer un nouveau conteneur (aussi 1, d' ailleurs)
Elizabeth Mattijsen
@ElizabethMattijsen Mais alors c'est le tableau qui fait le "lifting" oui?
Holli
Vous ne savez pas ce que vous entendez par «lever», mais si vous conteneurisez les valeurs à la création, alors oui.
Elizabeth Mattijsen
@Holli Merci pour votre réponse. Je ne sais pas si cela répond à la question. Votre réponse se concentre sur la mutabilité du conteneur, que je pense comprendre. Ce que je ne comprends pas, c'est pourquoi dans le premier exemple, le conteneur poussé $ i - ou mieux: sa valeur - est mis à jour après le push, tandis que dans le deuxième exemple, la valeur du conteneur poussé reste statiquement liée à la valeur pour le moment de la poussée. Le premier exemple a un sens pour moi (le conteneur est un pointeur sur l' Intobjet -> Intest remplacé dans la boucle -> le conteneur pointe vers nouveau Int), mais pas le second.
ozzy
@ Holli, je vais essayer de clarifier la question.
ozzy
3

Après avoir joué avec et réfléchi à ma question ci-dessus pendant un certain temps, je parierai une réponse ... C'est une pure conjecture de ma part, alors n'hésitez pas à dire que c'est insensé si c'est le cas, et si vous le savez, Pourquoi...

Dans le premier exemple, $iest défini en dehors de la portée lexicale de la boucle for. Par conséquent, $iexiste indépendamment de la boucle et de ses itérations. Quand $iest référencé depuis l'intérieur de la boucle, il n'y en a qu'un seul $iqui peut être affecté. C'est cela $iqui est poussé @bet dont le contenu est modifié par la suite dans la boucle.

Dans le deuxième exemple, $iest défini à l'intérieur de la portée lexicale de la boucle for. Comme l'a souligné @timotimo, le bloc pointé est appelé pour chaque itération, comme un sous-programme; $iest donc fraîchement déclaré pour chaque itération, et étendu au bloc respectif. Quand $iest référencé à l'intérieur de la boucle, la référence est au bloc-itération spécifique $i, qui cesserait normalement d'exister lorsque l'itération de boucle respective se termine. Mais parce qu'à un moment donné $iest poussé à @c, la référence au bloc spécifique à l' itération la $ivaleur de maintien 2ne peut être supprimé par le garbage collector après la fin de l'itération. Il restera en existence ..., mais restera différent des $iversions ultérieures.

ozzy
la source
@raiph Merci. Je ferai ça. Peut-être que quelqu'un avec plus de perspicacité que moi peut (re) formuler correctement la réponse. De toute façon, je n'accepterai pas ma propre réponse comme correcte jusqu'à ce qu'elle soit confirmée (ou améliorée) par ceux qui savent (plutôt que de deviner, comme moi).
ozzy