Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding a scalar to a sigilless variable (Perl 6)

Let me start by saying that I understand that what I'm asking about in the title is dubious practice (as explained here), but my lack of understanding concerns the syntax involved.

When I first tried to bind a scalar to a sigilless symbol, I did this:

my \a = $(3);

thinking that $(...) would package the Int 3 in a Scalar (as seemingly suggested in the documentation), which would then be bound to symbol a. This doesn't seem to work though: the Scalar is nowhere to be found (a.VAR.WHAT returns (Int), not (Scalar)).

In the above-referenced post, raiph mentions that the desired binding can be performed using a different syntax:

my \a = $ = 3;

which works. Given the result, I suspect that the statement can be phrased equivalently, though less concisely, as: my \a = (my $ = 3), which I could then understand.

That leaves the question: why does the attempt with $(...) not work, and what does it do instead?

like image 453
ozzy Avatar asked Jun 24 '18 10:06

ozzy


2 Answers

What $(…) does is turn a value into an item.
(A value in a scalar variable ($a) also gets marked as being an item)

say flat (1,2,      (3,4)  );
# (1 2 3 4)

say flat (1,2,    $((3,4)) );
# (1 2 (3 4))

say flat (1,2, item((3,4)) );
# (1 2 (3 4))

Basically it is there to prevent a value from flattening. The reason for its existence is that Perl 6 does not flatten lists as much as most other languages, and sometimes you need a little more control over flattening.


The following only sort-of does what you want it to do

my \a = $ = 3;

A bare $ is an anonymous state variable.

my \a = (state $) = 3;

The problem shows up when you run that same bit of code more than once.

sub foo ( $init ) {
  my \a = $ = $init; # my \a = (state $) = $init;

  (^10).map: {
    sleep 0.1;
    ++a
  }
}

.say for await (start foo(0)), (start foo(42));
# (43 44 45 46 47 48 49 50 51 52)
# (53 54 55 56 57 58 59 60 61 62)

# If foo(42) beat out foo(0) instead it would result in:
# (1 2 3 4 5 6 7 8 9 10)
# (11 12 13 14 15 16 17 18 19 20)

Note that variable is shared between calls.
The first Promise halts at the sleep call, and then the second sets the state variable before the first runs ++a.

If you use my $ instead, it now works properly.

sub foo ( $init ) {
  my \a = my $ = $init;

  (^10).map: {
    sleep 0.1;
    ++a
  }
}

.say for await (start foo(0)), (start foo(42));
# (1 2 3 4 5 6 7 8 9 10)
# (43 44 45 46 47 48 49 50 51 52)

The thing is that sigiless “variables” aren't really variables (they don't vary), they are more akin to lexically scoped (non)constants.

constant \foo = (1..10).pick; # only pick one value and never change it

say foo;
for ^5 {
  my \foo = (1..10).pick;     # pick a new one each time through

  say foo;
}

Basically the whole point of them is to be as close as possible to referring to the value you assign to it. (Static Single Assignment)

# these work basically the same
-> \a        {…}
-> \a is raw {…}
-> $a is raw {…}

# as do these
my \a  = $i;
my \a := $i;
my $a := $i;

Note that above I wrote the following:

my \a = (state $) = 3;

Normally in the declaration of a state var, the assignment only happens the first time the code gets run. Bare $ doesn't have a declaration as such, so I had to prevent that behaviour by putting the declaration in parens.

# bare $
for (5 ... 1) {
  my \a =        $  = $_; # set each time through the loop

  say a *= 2; # 15 12 9 6 3
}

# state in parens
for (5 ... 1) {
  my \a = (state $) = $_; # set each time through the loop

  say a *= 2; # 15 12 9 6 3
}

# normal state declaration
for (5 ... 1) {
  my \a =  state $  = $_; # set it only on the first time through the loop

  say a *= 2; # 15 45 135 405 1215
}
like image 119
Brad Gilbert Avatar answered Oct 07 '22 00:10

Brad Gilbert


Sigilless variables are not actually variables, they are more of an alias, that is, they are not containers but bind to the values they get on the right hand side.

my \a = $(3); 
say a.WHAT; # OUTPUT: «(Int)␤»
say a.VAR.WHAT; # OUTPUT: «(Int)␤»

Here, by doing $(3) you are actually putting in scalar context what is already in scalar context:

my \a = 3; say a.WHAT; say a.VAR.WHAT; # OUTPUT: «(Int)␤(Int)␤»

However, the second form in your question does something different. You're binding to an anonymous variable, which is a container:

my \a = $ = 3; 
say a.WHAT;    # OUTPUT: «(Int)␤»
say a.VAR.WHAT;# OUTPUT: «(Scalar)␤»

In the first case, a was an alias for 3 (or $(3), which is the same); in the second, a is an alias for $, which is a container, whose value is 3. This last case is equivalent to:

 my $anon = 3; say $anon.WHAT; say $anon.VAR.WHAT; # OUTPUT: «(Int)␤(Scalar)␤»

(If you have some suggestion on how to improve the documentation, I'd be happy to follow up on it)

like image 39
jjmerelo Avatar answered Oct 07 '22 01:10

jjmerelo