Assignment to a List Container Confusion

Note: You can't go wrong by reading @raiphs answer. However, I'll try to explain what is going on here and propose possible solutions.

In Raku, context is everything. Meaning that different contexts are triggered in different, well, contexts, implying different boxing or unboxing features of the data structure you're working with.

Let's look at this

my %syns-by-name = %(Bq => ["Bq", "becquerel", "becquerels"], C => ["C", "coulomb", "coulombs"],)

We do know we are in an "Associative" context because of the percentage marks both sides of the equal sign. We wouldn't even need it on the right hand side:

my %syns-by-name = Bq => ["Bq", "becquerel", "becquerels"], C => ["C", "coulomb", "coulombs"]

because, looking at the lhs, it's still a Associative, so we know what we have in our hands. Self same rhs code in this cotext:

my @list-of-signs = Bq => ["Bq", "becquerel", "becquerels"], C => ["C", "coulomb", "coulombs"] # [Bq => [Bq becquerel becquerels] C => [C coulomb coulombs]]

Will yield a list of pairs. So we check that what you mentioned in your OP:

Assignment to a List container (list-context) always triggers list assignment.

is tru-ish. This is a "list" container, we turn the rhs into a list just by context. There's no ambiguity of what's going on here: the rhs is a comma-separated list, the lhs is a Positional. So here you go.

The situation is slightly different in your code. It's maybe clarified a bit more if we use 2020.12

 my %syns-by-name = Bq => ["Bq", "becquerel", "becquerels"], C => ["C", "coulomb", "coulombs"]
my Str @res = %syns-by-name{'Bq'};
# Type check failed in assignment; expected Positional[Str] but got Array ($["Bq", "becquerel", ...)

Might not see the difference at first sight, so I'll highlight it here:

Type check failed in binding; expected Positional[Str] but got Array ($["Bq", "becquerel", ...)

That shows that the array is itemized, it's been boxed so that it can fit into a scalar-like thing like the value of a hash key. By default, Hash values are Scalar, with this composed into their definition:

role Associative[::TValue = Mu, ::TKey = Str(Any)] { }

So still what you mention holds: list assignment is triggered, as in "list context". However, the single item can be converted into a list only in a way: by making it the single element in a list. If you look at the error report closely, it's what it's saying: hey, you told me that you were gonna give me a list of Strs. This is not that! It's a list of (itemized) Arrays! This works:

my List @res = %syns-by-name{'Bq'};
# [(Bq becquerel becquerels)]

What we need to do is to «unbox» this thing, that is, to retrieve the Array that inhabits the Scalar. Easy-peasy:

my Str @res = %syns-by-name{'Bq'}<>;
# [Bq becquerel becquerels]

The de-cont operator (which maybe should be called "unbox" or "de-Scalarize" or "de-itemize" operator) does the right thing for you. That's probably not what you want, however. You want your arrays to be arrays. That' however, has proved a bit more tricky and I am going to need another SO question to solve it.


TL;DR If a non-native item is mutable via assignment, it's a Scalar. List assignment does not flatten Scalar items. [1]

An inkling

Consider this code:

my %map = :a;
%map<a> = 42;
say %map<a>; # 42

The assignment works because:

say %map<a>.VAR.WHAT; # (Scalar)

"list assignment"

Consider this code:

my $scalar = 1,2;       # Useless use of constant integer 2
say $scalar;            # 1

my @list1 = 1,2;        # "list assignment", so RHS is iterated
say @list1;             # [1 2]

So that's one difference between item and list assignment.

my @list3 = [1,2];      # Again, RHS is iterated
say @list3;             # [1 2]

my @list2 = (1,2);      # Again, RHS is iterated
say @list2;             # [1 2]

my @list4 = 1,(2,3);    # Again, RHS is iterated. Second element stays as a `List`.
say @list4;             # [1 (2 3)]

my @list5 = 1,[2,3];    # Again, RHS is iterated. Second element stays as an `Array`.
say @list5;             # [1 [2 3]]

If there's just one item listed on the RHS, and it isn't a Scalar, list assignment flattens it. But in all other scenarios list assignment doesn't flatten items.

my @list6 = $[1,2];     # RHS is a `Scalar`. So it doesn't get flattened.
say @list6;             # [[1 2]]

I'm sooo confused!

Golfing the situation in the Q:

my Str @res = %( :a[42,99] )<a>;

This yields the same kind of error.

Because:

say .VAR.WHAT given :a[42,99]<a>;       # (Array)
say .VAR.WHAT given (% = :a[42,99])<a>; # (Scalar)

Footnotes

[1] When surmise yields surprise, and you turn that into learning, you realize and idealize your investment in ERNing.

Tags:

Raku