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?
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:
First, as Liz notes, when trying to understand what's going on when you encounter a surprise, use dd
, not say
, because dd
focuses on the underlying reality.
Second, it's important to understand the role of Scalar
s in Raku, and how that sharply distinguishes Array
s from List
s.
Another way to see the underlying reality, and the role of Scalar
s, 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:
A Scalar
generally keeps quiet about itself
A Scalar
returns the value it contains in an r-value context, unless you explicitly seek it out with .VAR
.
Scalar
containers can be read/write or readonly
Until I wrote this answer, I had not cleanly integrated this aspect into my understanding of Raku's use of Scalar
s. Perhaps it's obvious to others but I feel it's worth mentioning here because the Scalar
indicated by the $(...)
display from dd
and .raku
is a readonly one -- you can't assign to it.
An Array
"autovivifies" (automatically creates and binds) a read/write Scalar
for each of its elements
If a value is assigned to an indexed position (say @foo[42]
) of a (non-native) Array
, then if that element does not currently :exist
(ie @foo[42]:exists
is False
), then a fresh read/write Scalar
is "autovivified" as the first step in processing the assignment.
A List
never autovivifies a Scalar
for any of its elements
When a value is "assigned" (actually bound, even if the word "assigned" is used) to an indexed position in a List
, no autovivification ever occurs. A List
can include Scalar
s, including read/write ones, but the only way that can happen is if an existing read/write Scalar
is "assigned" to an element (indexed position), eg my @foo := (42, $ = 99); @foo[1] = 100; say @foo; # (42 100)
.
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.)