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?
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" :-)
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, but...
... was it?
==> 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)
say @foo[1].VAR.WHAT; # (Scalar) <== In reality it 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`s do *not* make `Scalar`s
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] = ...; # Fails no matter what `@foo2[1]` and `...` are.
@foo2[1] := ...; # Fails no matter what `@foo2[1]` and `...` are.
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" 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" (automatically created and bound to that indexed position) 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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With