Binding a scalar to a sigilless variable (Perl 6)
Asked Answered
A

2

7

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?

Ariannearianrhod answered 24/6, 2018 at 10:18 Comment(0)
M
6

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
}
Munafo answered 24/6, 2018 at 17:19 Comment(1)
Awesome answer! Clarified some of my misconceptions, and gave me more to think about. Thanks.Ariannearianrhod
A
2

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)

Arturoartus answered 24/6, 2018 at 10:29 Comment(2)
Hi jjmerelo: I understand your entire answer, except for the part that I think is crucial: the part where you mention that $(...) puts the Int in a "scalar context". The docs say "A literal Scalar may be placed around a literal value by enclosing the value in $(…)." Obviously, that doesn't mean what I thought it did; but it doesn't talk about "scalar context" either. When I search for the meaning of the term "context", I find no clear definition, and it seems I'm not alone. So I still have difficulty understanding what it is that $(...) does.Ariannearianrhod
@Ozzy I'm still working on that issue. Takes some time. Anyway, my point was that putting a $() around a literal does not create a container, which was maybe what you were looking of.Arturoartus

© 2022 - 2024 — McMap. All rights reserved.