Search code examples
rakuvariable-binding

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?


Solution

  • 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
    }