Wartość skalarna jest zmieniana po naciśnięciu lub nie… (Raku)

12

Mam trudności ze zrozumieniem, kiedy i dlaczego wartość trzymana przez przepchnięty Scalarpojemnik ma wpływ po wypchnięciu. Spróbuję zilustrować problem, na który wpadłem w bardziej skomplikowanym kontekście, w dwóch stylizowanych przykładach.

* Przykład 1 * W pierwszym przykładzie skalar $ijest wypychany na tablicę @bjako część List. Po wypchnięciu wartość przechowywana przez skalar jest wyraźnie aktualizowana w późniejszych iteracjach pętli for za pomocą $i++instrukcji. Te aktualizacje mają wpływ na wartość w tablicy @b: na końcu pętli for @b[0;0]jest równa 3i już nie 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;

Przykład wyjścia 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

* Przykład 2 * W drugim przykładzie skalar $ijest zmienną pętli. Nawet jeśli $ijest aktualizowana po to został zepchnięty (teraz domyślnie zamiast jawnie), wartość $iw tablicy @cma nie zmienia się po naciśnięciu; czyli po pętli, to jeszcze 2nie 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;;

Przykład wyjścia 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

Pytanie: Dlaczego $iw @bw przykładzie 1 zaktualizowanej po naciśnięciu, podczas gdy $iw @cw przykładzie 2 nie jest?

edycja : Po komentarzu @ timotimo zamieściłem wynik .WHEREw przykładach. Pokazuje to (KTÓRA / logiczna) tożsamość skalarna $ipozostaje taka sama, podczas gdy jej adres pamięci zmienia się w różnych iteracjach pętli. Nie wyjaśnia to jednak, dlaczego w przykładzie 2 wypchnięty skalar pozostaje związany z tą samą tożsamością KTÓRĄ w połączeniu ze starym adresem („448).

ozzy
źródło
2
mogę odpowiedzieć na pytanie, dlaczego KTÓRZY wydaje się być taki sam; spójrz na implementację: github.com/rakudo/rakudo/blob/master/src/core.c/Scalar.pm6#L8 - to zależy tylko od użytego deskryptora, który jest małym obiektem przechowującym takie rzeczy jak nazwa zmienna i ograniczenie typu. jeśli użyjesz .WHEREzamiast tego .WHICH, zobaczysz, że skalar jest w rzeczywistości innym obiektem za każdym razem wokół pętli. Dzieje się tak, ponieważ spiczaste bloki są „wywoływane”, a podpis jest „związany” przy każdym wywołaniu.
timotimo
@raiph Podczas pętli w przykładzie 1 pokazano ten sam wzorzec jak w przykładzie 2: oba mają zmienione adresy zgłoszone przez .WHERE, co oznacza, zgadzam się. Ale samo w sobie nie wyjaśnia, dlaczego Przykład 2 ma inne zakończenie niż Przykład 1.
ozzy

Odpowiedzi:

5

Wartość skalarna to tylko kontener. Możesz myśleć o nich jak o inteligentnym wskaźniku, a nie o prymitywnej wartości.

Jeśli wykonasz zadanie

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

zmieniasz wartość skalarów, pojemnik pozostaje taki sam.

Rozważać

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

i

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;

Oba działają zgodnie z oczekiwaniami. Ale: W obu przypadkach rzeczy na liście nie można już modyfikować, ponieważ nie ma kontenera.

@b[4;0] = 99; 

dlatego umrze. Więc po prostu użyj zmiennej pętli, prawda?

Nie.

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

nawet jeśli przejdziemy przez listę zmiennych rzeczy.

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

Zatem nie ma tu żadnego aliasingu, zamiast tego zmienną pętli jest zawsze ten sam kontener i przypisywane są wartości pochodzące z innych kontenerów.

Możemy to jednak zrobić.

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

Sposobem na zmodowanie „rzeczy” jest użycie zmiennej pośredniej.

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

działa w porządku. Lub krótszy i bardziej oryginalny w kontekście

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

Zobacz też:

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

Holli
źródło
1
Zamiast ($x,1), można również zrobić [$x,1]który będzie utworzyć nowy pojemnik (także 1, BTW)
Elizabeth Mattijsen
@ElizabethMattijsen Ale to tablica robi „podnoszenie” tak?
Holli
Nie wiem, co rozumiesz przez „podnoszenie”, ale jeśli konteneryzujesz wartości przy tworzeniu, to tak.
Elizabeth Mattijsen,
@Holli Dziękujemy za odpowiedź. Nie jestem jednak pewien, czy to rozwiązuje pytanie. Twoja odpowiedź koncentruje się na zmienności pojemnika, którą, jak sądzę, rozumiem. Nie rozumiem, dlaczego w pierwszym przykładzie wypchnięty pojemnik $ i - lub lepiej: jego wartość - jest aktualizowany po wypchnięciu, podczas gdy w drugim przykładzie wartość wypchniętego pojemnika pozostaje w tej chwili statycznie związana z wartością push. Pierwszy przykład ma dla mnie sens (kontener jest wskaźnikiem do Intobiektu -> Intzostaje zastąpiony pętlą -> kontener wskazuje na nowy Int), ale drugi nie.
ozzy
@Holli Postaram się wyjaśnić pytanie.
ozzy
3

Po pewnym czasie zabawy i zastanowienia się nad moim powyższym pytaniem, postawię odpowiedź ... To z mojej strony czysta hipoteza, więc nie wahaj się powiedzieć, że to nie ma sensu, jeśli tak, a jeśli się dowiesz, dlaczego...

W pierwszym przykładzie $ijest zdefiniowany poza zakresem leksykalnym pętli for. W konsekwencji $iistnieje niezależnie od pętli i jej iteracji. Gdy $ijest wywoływany z wnętrza pętli, istnieje tylko jeden, na $iktóry można wpłynąć. To jest to, $ico jest wpychane @b, a jego zawartość jest następnie modyfikowana w pętli.

W drugim przykładzie $izdefiniowano wewnątrz zakresu leksykalnego pętli for. Jak wskazał @timotimo, wskazany blok jest wywoływany dla każdej iteracji, jak podprogram; $ijest zatem świeżo zadeklarowany dla każdej iteracji i objęty odpowiednim blokiem. Kiedy $iodwołuje się do pętli, odnosi się do specyficznej dla iteracji bloku $i, która normalnie przestałaby istnieć, gdy kończy się odpowiednia iteracja pętli. Ale ponieważ w pewnym momencie $ijest on popychany @c, odwołanie do $iwartości trzymania specyficznej dla iteracji bloku 2nie może zostać usunięte przez śmieciarz po zakończeniu iteracji. Będzie istniał ... ale nadal będzie inny niż $iw późniejszych iteracjach.

ozzy
źródło
@raiph Thanks. Zrobię to. Być może ktoś z większym wglądem niż ja może (ponownie) poprawnie sformułować odpowiedź. W każdym razie nie zaakceptuję mojej odpowiedzi jako poprawnej, dopóki nie zostanie potwierdzona (lub poprawiona) przez tych, którzy wiedzą (zamiast zgadywać, jak ja).
ozzy