Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding operator used in multi-variable initialization is working only when variables are also declared in the same line

In the following example:

my $i1 = 1;
my $i2 = 2;

my ($v1, $v2);

($v1, $v2) := ($i1, $i2);

say $v1;
say $v2;

the code throws with a compilation Error:

===SORRY!=== Error while compiling ...
Cannot use bind operator with this left-hand side
at ...:8
------> ($v1, $v2) := ($i1, $i2)<HERE>;

When I put declaration and binding in one line, then it runs successfully:

my $i1 = 1;
my $i2 = 2;

my ($v1, $v2) := ($i1, $i2);

say $v1;  # 1
say $v2;  # 2

$i1 = 11;
$i2 = 22;

say $v1;  # 1
say $v2;  # 2

Nevertheless the last successful example shows that the variables $v1, $v2 are NOT bound to the variables $i1, $i2, but rather they've got their respective values assigned.

It seems like there is NO actual binding going on but a plain assignment!

Does anyone have an explanation for the mechanics behind this behavior and why is it, that we must also "declare" the variables in the same line, in order for the code to run?

like image 280
jakar Avatar asked Mar 03 '23 03:03

jakar


2 Answers

In the first case, it's simply saying that you can't bind that list after creation. When you bind a list to another, you're not binding every variable, you're binding its container; := binds its left hand side to its right hand side, making them effectively the same thing; if they are not the same thing to start with, you can't do that later.

So if you want to bind a container to another, you will effectively have to declare and bind it, by itself, to whatever you want it bound.

That applies also to the second case. It makes the list ($v1, $v2) the same thing as the right hand side. This should have probably thrown an error, but it will simply not bind every individual container.

like image 200
jjmerelo Avatar answered Mar 04 '23 18:03

jjmerelo


It isn't about being declared on the same line.

It is about binding in a declaration, vs binding to a list.


Binding and assignment in a declaration produces different code to regular binding or assignment.

my \abc = 'def';

abc  = 5; # ERROR: Cannot modify an immutable Str
\abc = 5; # ERROR: Cannot modify an immutable Capture


for ^3 {
  state $i = 0;
  say ++$i; # 1␤2␤3␤
}

for ^3 {
  state $i;
  $i = 0;
  say ++$i; # 1␤1␤1␤
}

In particular I don't think your code does what you think it does.
Either that or your expectations are way off.

my $i1 = 1;
my $i2 = 2;

my ($v1, $v2) := ($i1, $i2);

say $v1; # 1
say $v2; # 2

$i2 = 3;
say $v2; # 2

# $v2 = 3; # ERROR: Cannot assign to a readonly variable or a value

It doesn't bind the variables to the other variables, it binds to the current values inside of them.

If that is truly what you wanted you can bind to a Signature object.

my $i1 = 1;
my $i2 = 2;

my ($v1, $v2);

:($v1, $v2) := ($i1, $i2); # <--

say $v1; # 1
say $v2; # 2

$i2 = 3;
say $v2; # 2

# $v2 = 3; # ERROR: Cannot assign to a readonly variable or a value

The whole reason that binding exists is to bind a variable to another variable.

sub var-name ( $a is rw ){ # <-- Bind $a to the incoming variable
  $a.VAR.name;
}

my $foo;
say var-name $foo; # $foo

In the above example $a is bound to $foo.
Anything you do to $foo also happens to $a and vice-versa.

You can also do that manually with the binding operator :=.

my ($a,$b,$c);

{
  my $v := $a;
  say $v.VAR.name; # $a

  $v := $b;
  say $v.VAR.name; # $b

  $v := $c;
  say $v.VAR.name; # $c
}

When you bind something it takes the left thing and points it to the thing at the right.

my ($a,$b,$c,$v);

$v := ($a,$b,$c);

The $v variable is now bound to the list containing the variables $a, $b, $c.

Note that the list is not the values inside of the variables, but the actual variables themselves.

say $v[0].VAR.name; # $a
say var-name $v[1]; # $b

say $v.^name; # List

$v[2] = 7;
say $c; # 7

When you try binding a List to another List, you are trying to overwrite the List on the left. Not the variables inside the List.

Even if you managed to do that it would be pointless. About the only thing it would do is to have the left list get garbage collected a little bit sooner.

When you see my $a, there is a few things happening.

  1. A pointer is created and it is added under the name of $a in the namespace.
    (close enough anyway)
  2. A Scalar object is created. Its attribute name is set to $a.
  3. That pointer starts pointing to the Scalar.

Almost of the behaviours of a variable are actually behaviours of that Scalar container.

Except one.

When you use the binding operator := you are taking that pointer and pointing it to a new thing.

$a := $b;

# pseudo code
$a.POINTER = $b.POINTER;

A List is not a pointer. (It can be pointed to though.)

(Note that the declaration is also involved in the initial assignment into the Scalar.)


With assignment = the variables or values get a chance to choose what happens.

If you assign to an Array each of the Scalar containers get assigned to each of the values on the right.

my @a;
@a = 0,1,2,3;

my $v := @a[4];
$v = 4;

say @a.raku; # [0, 1, 2, 3, 4]



sub assign ( Positional $dest is raw, Positional $source is raw ){
  #for $source.keys -> $index {
  #  my $v := $dest[$index];
  #  $v = $source[$index];
  #}
  $dest.STORE($source)
}

@a = ();
assign @a, (5,6,7,8);

say @a.raku; # [5, 6, 7, 8]

When you use assignment = you are basically calling the STORE method on the left thing. It gets to decide what happens. (It is actually a little bit more complex than that.)

In the case of an Array or List, it goes through the elements assigning each one in turn. (Assuming that element is actually a Scalar container.)

 my ($a,$b);

 ($a,$b) = 1,2;

 ($a,$b).STORE( (1,2) );

 my $list = ($a,$b);
 $list.STORE( (1,2) );

When you bind you are just plain overwriting the thing on the left.

like image 27
Brad Gilbert Avatar answered Mar 04 '23 17:03

Brad Gilbert