Search code examples
variable-assignmentrakudynamic-variablesrakudo

Dynamic variables, CALLERS, Scalars, and assignment


I recently noticed that that re-initializing dynamic variables does not have the semantics I expected in most cases using assignment (binding works the way I expected it to, however).

Specifically, in this code:

sub g {
    my $*i  = CALLERS::<$*i>  // 0;
    my $*a1 = CALLERS::<$*a1> // Array.new;
    my @*a2 = CALLERS::<@*a2> // Array.new;
    $*i++;
    $*a1.push: 'v1';
    @*a2.push: 'v2';
    dd $*i;
    dd $*a1;
    dd @*a2;
}
sub f {
    my $*i = 0;
    my $*a1 = Array.new;
    my @*a2 = Array.new;
    g; g; g;
}
f

I expected output of 3, ["v1", "v1", "v1"], and ["v2", "v2", "v2"] but instead get 1, $["v1", "v1", "v1"], ["v2"]. Switching to binding solves the issue, so there's no problem I'm trying to solve – but I would very much like to understand why assignment doesn't work here. I notice that a Scalar pointing to an Array works, but a Scalar pointing to an Int doesn't. But in either case, I would have thought that the newly assigned variable would receive the value from CALLERS. What am I missing about the semantics of assignment?


Solution

  • What am I missing about the semantics of assignment?

    I think what you're missing, doesn't have anything to do with dynamic variables per se. I think what you're missing is the fact that:

    my @a = Array.new;
    

    is basically a noop. Because of the single argument rule, it is the same as:

    my @a = ();
    

    which is the same as:

    my @a;
    

    So, in your example in sub f, the:

    my @*a2 = Array.new;
    

    in is just setting up an empty array in a dynamic variable.

    Then in sub g the:

    my @*a2 = CALLERS::<@*a2> // Array.new;
    

    is just basically doing (because of the single argument rule):

    my @*a2;
    

    and hence you don't see the pushes you've done before, because every call it starts out fresh.

    Regarding the value of $*i in sub g: that also is just incrementing a copy of the callers $*i, so the value remains at 1 in every call.

    The reason that $*a1 works, is that containerization stops the flattening behaviour of the single argument rule. Observe the difference between:

    sub a(+@a) { dd @a }; a [2,3,4];  # [2,3,4]
    

    and:

    sub a(+@a) { dd @a }; a $[2,3,4];  # [[2,3,4],]