Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scalar value being affected after push, or not... (Raku)

I have difficulty understanding when and why the value held by a pushed Scalar container is affected after the push. I'll try to illustrate the issue that I ran into in a more complicated context in two stylized examples.

*Example 1 * In the first example, a scalar $i is pushed onto an array @b as part of a List. After the push, the value held by the scalar is explicitly updated in later iterations of the for loop using the $i++ instruction. These updates have an effect on the value in the array @b: at the end of the for loop, @b[0;0] is equal to 3, and no longer to 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;

Output example 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

* Example 2 * In the second example, the scalar $i is the loop variable. Even though $i is updated after it has been pushed (now implicitly rather than explicitly), the value of $i in array @c does not change after the push; i.e. after the for loop, it is still 2, not 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;;

Output example 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: Why is $i in @b in example 1 updated after the push, while $i in @c in example 2 is not?

edit: Following @timotimo's comment, I included the output of .WHERE in the examples. This shows the (WHICH/logical) scalar-identity of $i stays the same, while its memory address changes through the various loop iterations. But it does not explain why in example 2 the pushed scalar remains tied to the same WHICH-identity in combination with an old address ("448).

like image 632
ozzy Avatar asked Oct 05 '19 15:10

ozzy


2 Answers

A scalar value is just a container. You can think of them as a kind of smart pointer, rather than a primitive value.

If you do an assignment

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

you are changing the scalars value, the container stays the same.

Consider

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

and

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;

Both of which work as expected. But: In both cases, the thing in the list is not mutable anymore, because there is no container.

@b[4;0] = 99; 

will therefore die. So just use the loop variable then, right?

No.

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

even if we iterate over a list of mutable things.

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

So there is no aliasing happening here, instead the loop variable is always the same container and gets values assigned that come from the other containers.

We can do this though.

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

A way to make "the thing" mutable is using an intermediate variable.

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

works fine. Or shorter and more in the original context

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

See also:

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

like image 194
Holli Avatar answered Nov 10 '22 10:11

Holli


After playing with and thinking about my above question for some time, I'll wager an answer... It's pure conjecture on my part, so please feel free to say it's non-sense if it is, and if you happen to know, why...

In the first example, $i is defined outside of the lexical scope of the for loop. Consequently, $i exists independent of the loop and its iterations. When $i is referenced from inside the loop, there is only one $i that can be affected. It is this $i that gets pushed into @b, and has its contents modified afterwards in the loop.

In the second example, $i is defined inside the lexical scope of the for loop. As @timotimo pointed out, the pointed block get's called for each iteration, like a subroutine; $i is therefore freshly declared for each iteration, and scoped to the respective block. When $i is referenced inside the loop, the reference is to the block-iteration-specific $i, which would normally cease to exist when the respective loop iteration ends. But because at some point $i is pushed to @c, the reference to the block-iteration-specific $i holding value 2 cannot be deleted by the garbage collector after termination of the iteration. It will stay in existence..., but still be different from $i in later iterations.

like image 28
ozzy Avatar answered Nov 10 '22 09:11

ozzy