I want to make a hash of sets. Well, SetHashes, since they need to be mutable.
In fact, I would like to initialize my Hash with multiple identical copies of the same SetHash.
I have an array containing the keys for the new hash: @keys
And I have my SetHash already initialized in a scalar variable: $set
I'm looking for a clean way to initialize the hash.
This works:
my %hash = ({ $_ => $set.clone } for @keys);
(The parens are needed for precedence; without them, the assignment to %hash
is part of the body of the for
loop. I could change it to a non-postfix for loop or make any of several other minor changes to get the same result in a slightly different way, but that's not what I'm interested in here.)
Instead, I was kind of hoping I could use one of Raku's nifty hyper-operators, maybe like this:
my %hash = @keys »=>» $set;
That expression works a treat when $set
is a simple string or number, but a SetHash?
Array >>=>>> SetHash can never work reliably: order of keys in SetHash is indeterminate
Good to know, but I don't want it to hyper over the RHS, in any order. That's why I used the right-pointing version of the hyperop: so it would instead replicate the RHS as needed to match it up to the LHS. In this sort of expression, is there any way to say "Yo, Raku, treat this as a scalar. No, really."?
I tried an explicit Scalar wrapper (which would make the values harder to get at, but it was an experiment):
my %map = @keys »=>» $($set,)
And that got me this message:
Lists on either side of non-dwimmy hyperop of infix:«=>» are not of the same length while recursing
left: 1 elements, right: 4 elements
So it has apparently recursed into the list on the left and found a single key and is trying to map it to a set on the right which has 4 elements. Which is what I want - the key mapped to the set. But instead it's mapping it to the elements of the set, and the hyperoperator is pointing the wrong way for that combination of sizes.
So why is it recursing on the right at all? I thought a Scalar container would prevent that. The documentation says it prevents flattening; how is this recursion not flattening? What's the distinction being drawn?
The error message says the version of the hyperoperator I'm using is "non-dwimmy", which may explain why it's not in fact doing what I mean, but is there maybe an even-less-dwimmy version that lets me be even more explicit? I still haven't gotten my brain aligned well enough with the way Raku works for it to be able to tell WIM reliably.
I'm looking for a clean way to initialize the hash.
One idiomatic option:
my %hash = @keys X=> $set;
See X
metaoperator.
The documentation says ... a
Scalar
container ... prevents flattening; how is this recursion not flattening? What's the distinction being drawn?
A cat is an animal, but an animal is not necessarily a cat. Flattening may act recursively, but some operations that act recursively don't flatten. Recursive flattening stops if it sees a Scalar
. But hyperoperation isn't flattening. I get where you're coming from, but this is not the real problem, or at least not a solution.
I had thought that hyperoperation had two tests controlling recursing:
Is it hyperoperating a nodal
operation (eg .elems
)? If so, just apply it like a parallel shallow map
(so don't recurse). (The current doc quite strongly implies that nodal
can only be usefully applied to a method, and only a List
one (or augmentation thereof) rather than any routine that might get hyperoperated. That is much more restrictive than I was expecting, and I'm sceptical of its truth.)
Otherwise, is a value Iterable
? If so, then recurse into that value. In general the value of a Scalar
automatically behaves as the value it contains, and that applies here. So Scalar
s won't help.
A SetHash
doesn't do the Iterable
role. So I think this refusal to hyperoperate with it is something else.
I just searched the source and that yields two matches in the current Rakudo source, both in the Hyper module, with this one being the specific one we're dealing with:
multi method infix(List:D \left, Associative:D \right) {
die "{left.^name} $.name {right.^name} can never work reliably..."
}
For some reason hyperoperation explicitly rejects use of Associative
s on either the right or left when coupled with the other side being a List
value.
Having pursued the "blame" (tracking who made what changes) I arrived at the commit "Die on Associative <<op>>
Iterable" which says:
This can never work due to the random order of keys in the
Associative
. This used to die before, but with a very LTA error about aPair.new()
not finding a suitable candidate.
Perhaps this behaviour could be refined so that the determining factor is, first, whether an operand does the Iterable
role, and then if it does, and is Associative
, it dies, but if it isn't, it's accepted as a single item?
A search for "can never work reliably" in GH/rakudo/rakudo issues yields zero matches.
Maybe file an issue? (Update I filed "RFC: Allow use of hyperoperators with an Associative that does not do Iterable role instead of dying with "can never work reliably".)
For now we need to find some other technique to stop a non-Iterable
Associative
being rejected. Here I use a Capture
literal:
my %hash = @keys »=>» \($set);
This yields: {a => \(SetHash.new("b","a","c")), b => \(SetHash.new("b","a","c")), ...
.
Adding a custom op unwraps en passant:
sub infix:« my=> » ($lhs, $rhs) { $lhs => $rhs[0] }
my %hash = @keys »my=>» \($set);
This yields the desired outcome: {a => SetHash(a b c), b => SetHash(a b c), ...
.
my %hash = ({ $_ => $set.clone } for @keys);
(The parens seem to be needed so it can tell that the curlies are a block instead of a
Hash
literal...)
No. That particular code in curlies is a Block
regardless of whether it's in parens or not.
More generally, Raku code of the form {...}
in term position is almost always a Block
.
For an explanation of when a {...}
sequence is a Hash
, and how to force it to be one, see my answer to the Raku SO Is that a Hash
or a Block
?.
Without the parens you've written this:
my %hash = { block of code } for @keys
which attempts to iterate @keys
, running the code my %hash = { block of code }
for each iteration. The code fails because you can't assign a block of code to a hash.
Putting parens around the ({ block of code } for @keys)
part completely alters the meaning of the code.
Now it runs the block of code for each iteration. And it concatenates the result of each run into a list of results, each of which is a Pair
generated by the code $_ => $set.clone
. Then, when the for
iteration has completed, that resulting list of pairs is assigned, once, to my %hash
.
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