'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 1:[1]
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
, notsay
, becausedd
focuses on the underlying reality.Second, it's important to understand the role of
Scalar
s in Raku, and how that sharply distinguishesArray
s fromList
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 itselfA
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 readonlyUntil 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 theScalar
indicated by the$(...)
display fromdd
and.raku
is a readonly one -- you can't assign to it.An
Array
"autovivifies" (automatically creates and binds) a read/writeScalar
for each of its elementsIf 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
isFalse
), then a fresh read/writeScalar
is "autovivified" as the first step in processing the assignment.A
List
never autovivifies aScalar
for any of its elementsWhen a value is "assigned" (actually bound, even if the word "assigned" is used) to an indexed position in a
List
, no autovivification ever occurs. AList
can includeScalar
s, including read/write ones, but the only way that can happen is if an existing read/writeScalar
is "assigned" to an element (indexed position), egmy @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.)
Solution 2:[2]
If you remove the +
from the Z
, and you use dd
instead of say
, it may become clearer:
dd (3,3) Z @foo[1]; # ((3, $(2, 2)),).Seq
So in this case, you get a list with 3
and (2,2)
. Note the $
before the (2,2)
: that means it's itemized: to be considered a single item.
Now with Z+
, instead of creating a list, you're going to add the values.
When you write:
say 3 + (42,666); # 5
you get 5
because you're adding the number of elements in the list to 3
. That's why you're winding up with 5
in your example as well, not because the values in the list are 2
.
In the other cases the Z
operators sees non-itemized lists and so will iterate over its elements as you expected.
In case of doubt, make sure that you use dd
instead of say
in debugging: it will give you the nitty gritty of an expression, and not just a "gist" :-)
Solution 3:[3]
Starting from:
my @foo = (1,1), (2,2);
Do:
say (3,3) Z+ @foo[1][*]; # (5 5)
OR
say (3,3) Z+ @foo[1][]; # (5 5)
OR
say (3,3) Z+ @foo[1]<>; # (5 5)
OR
say (3,3) Z+ @foo[1]:v; # (5 5)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Elizabeth Mattijsen |
Solution 3 | jubilatious1 |