arrayslistrakurakudo

Raku list addition operator `Z+` 'fails' unless one of the lists is forced


I'm struggling to understand why the zip-add Z+ operator does not work on some cases.

I have some 2-element lists that I'd like to sum.

These work as expected whether I use lists or arrays:

say (1, 2) Z+ (3, 4) # (4, 6)
say [1, 2] Z+ (3, 4) # (4, 6)
say [1, 2] Z+ [3, 4] # (4, 6)
say (1, 2) Z+ [3, 4] # (4, 6)

Now we will do the same thing but I'll change the right operand with a value stored elsewhere. In this case I have an array of lists:

my @foo = (1,1), (2,2);
say @foo.WHAT;              # (Array)
say @foo[1].WHAT;           # (List)
say @foo[1];                # (2,2)
say (3,3) Z+ @foo[1];       # (5)  ???

Which gives the unexpected (at least for me :)) result of (5).

There are a couple of ways to fix this.

First one is to force the got element to be a list:

my @foo = (1,1), (2,2);
say @foo.WHAT;              # (Array)
say @foo[1].WHAT;           # (List)   <== It was already a list, but...
say @foo[1];                # (2,2)
say (3,3) Z+ @foo[1].list;  # <== changed. (5,5)

And the other one is change the @foo definition to be a list instead of an array (either by is List or by binding := the value)

my @foo is List = (1,1), (2,2);   # <=== Changed
say @foo.WHAT;              # (Array)
say @foo[1].WHAT;           # (List)   <== It was already a list
say @foo[1];                # (2,2)
say (3,3) Z+ @foo[1];       # (5,5)

Why the first case didn't work?


Solution

  • Another way of looking at things...

    my @foo = (1,1), (2,2);
    say @foo.WHAT;              # (Array)
    say @foo[1].WHAT;           # (List)   <== It was already a list, right?
    

    ==> No, it wasn't.

    This is the primary key to your question in two respects:


    Another way to see the underlying reality, and the role of Scalars, is to expand your examples a little:

    my @foo = (1,1), (2,2);
    say @foo.WHAT;              # (Array)  <== Top level elements "autovivify" as `Scalar`s
    say @foo[1].VAR.WHAT;       # (Scalar) <== The element was a `Scalar`, not a `List`
    say @foo[1].WHAT;           # (List)   <== The `Scalar` returns the value it contains
    @foo[1] = 42;               # Works.   <== The `Scalar` supports mutability
    
    my @foo2 is List = (1,1), (2,2);
    say @foo2.WHAT;              # (List)  <== `List` elements *don't* "autovivify"
    say @foo2[1].VAR.WHAT;       # (List)  <== `VAR` on a non-`Scalar` is a no op
    say @foo2[1].WHAT;           # (List)  <== This time `@foo2[1]` IS a *`List`*
    @foo2[1] = ...;              # Attempt to assign to `List` bound to `@foo2[1]` fails
    @foo2[1] := ...;             # Attempt to bind to `@foo2[1]` element fails
    

    I'll draw attention to several aspects of the above:


    And now we can understand your code that yields (5):

    my @foo = (1,1), (2,2);     # `@foo` is bound to a fresh non-native `Array`
    say @foo[1].VAR.WHAT;       # (Scalar) -- @foo[1] is an autovivified `Scalar`
    say @foo[1];                # (2,2) -- `say` shows value contained by `Scalar`
    
    say (3,3) Z+ @foo[1];       # (5) --- because it's same as follows:
    
    say +$(2,2);                # 2 -- number of elements in a two element list †
    say (3,3) Z+ 2;             # (5) -- `Z` stops if either side exhausted
    

    We're applying a coercive numeric operation (+) to a list (Positional value), not to its elements. A list, coerced to a number, is its "length" (count of elements). (Certainly for a non-sparse one. I'm not sure about sparse ones.)