10

In the Hash documentation, the section on Object keys seems to imply that you can use any type as a Hash key as long as you indicate but I am having trouble when trying to use an array as the key:

> my %h{Array};
{}
> %h{[1,2]} = [3,4];
Type check failed in binding to parameter 'key'; expected Array but got Int (1)
in block <unit> at <unknown file> line 1

Is it possible to do this?

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
Hunter McMillen
  • 59,865
  • 24
  • 119
  • 170
  • If you change `%h{[1,2]} = [3,4];` to `%h{item [1,2]} = [3,4];` it prevents the Array from being expanded into a slice and keeps it as an Array so it can be used as a key. – callyalater Apr 26 '18 at 21:04
  • @callyalater: true, but it won't be of much use :-(. See my answer. – Elizabeth Mattijsen Apr 26 '18 at 21:20
  • @raiph For my use case I can guarantee that the inputs will be integers, so using either `Set([1,2])` or `~[1, 2]` meets my needs perfectly. `.perl` seems interesting but I am not sure how often I will use it practically during normal development. – Hunter McMillen Apr 30 '18 at 01:00
  • Thanks. I removed my earlier comment and answer suggesting `~`, but as you presumably know I evolved my answer from a `~` prefix to a `.perl` postfix. That's because, afaik, other than brevity, it's at least as good as, and in many scenarios better than the other two hacks, namely regular stringification (`~`) or `.Set` per Liz's suggestion. `.perl` distinguishes `[1,2]` and `[2,1]`, which `.Set` does not and distinguishes `<<'a a' 'b'>>` and `<<'a' 'a b'>>` which regular stringification (with `~`) does not. Anyhoo, as you say, `~` will work fine if all your array/list elements are integers. – raiph Apr 30 '18 at 06:22
  • @raiph I also think that it is important to indicate to later answer-seekers that the answer is *no*, you cannot directly use an Array as a Hash key due to what is explained in Elizabeth's answer. – Hunter McMillen Apr 30 '18 at 12:57

2 Answers2

8

The [1,2] inside the %h{[1,2]} = [3,4] is interpreted as a slice. So it tries to assign %h{1} and %{2}. And since the key must be an Array, that does not typecheck well. Which is what the error message is telling you.

If you itemize the array, it "does" work:

my %h{Array};
%h{ $[1,2] } = [3,4];
say %h.perl;  # (my Any %{Array} = ([1, 2]) => $[3, 4])

However, that probably does not get what you want, because:

say %h{ $[1,2] };  # (Any)

That's because object hashes use the value of the .WHICH method as the key in the underlying array.

say [1,2].WHICH; say [1,2].WHICH;
# Array|140324137953800
# Array|140324137962312

Note that the .WHICH values for those seemingly identical arrays are different. That's because Arrays are mutable. As Lists can be, so that's not really going to work.

So what are you trying to achieve? If the order of the values in the array is not important, you can probably use Sets as keys:

say [1,2].Set.WHICH; say [1,2].Set.WHICH
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2

Note that these two .WHICHes are the same. So you could maybe write this as:

my %h{Set};
dd %h{ (1,2).Set } = (3,4); # $(3, 4)
dd %h; # (my Any %{Set} = ((2,1).Set) => $(3, 4))

Hope this clarifies things. More info at: https://docs.raku.org/routine/WHICH

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • 2
    Great answer, but folk might read what you've written as saying one can't use arrays. But it's just literals that's the problem -- `my %h{Array}; my @array = 5,6; %h{ $@array } = 'foo'; say %h{ $@array }; # foo` works fine, and you can even modify the array and it'll still work: `@array = 7,8; @array.append: 9,42; say %h{ $@array }; # foo`. Also: `%h { $@array } = $@array` would presumably work to keep the array as key and the array's values as the associated values (not tested, I need to go to bed). – raiph Apr 26 '18 at 22:09
  • 1
    @raiph While that does appear to work on insertion, you need to pass an array to _retrieve_ the value as well which makes it pretty awkward to use. I'll give the Set solution a try and report back. – Hunter McMillen Apr 26 '18 at 23:48
  • 1
    @raiph: as Hunter McMillen found out, even if you don't use literal arrays, you would have to use the *same* array to get at the value. Which I think won't happen in real life really often. Hence my suggestion for `Set`, which only makes sense if the order of the values in the array doesn't matter. – Elizabeth Mattijsen Apr 27 '18 at 16:11
3

If you are really only interested in use of an Object Hash for some reason, refer to Liz's answer here and especially the answers to, and comments on, a similar earlier question.

The (final1) focus of this answer is a simple way to use an Array like [1,'abc',[3/4,Mu,["more",5e6],9.9],"It's {<sunny rainy>.pick} today"] as a regular string hash key.

The basic principle is use of .perl to approximate an immutable "value type" array until such time as there is a canonical immutable Positional type with a more robust value type .WHICH.

A simple way to use an array as a hash key

my %hash;
%hash{ [1,2,3].perl } = 'foo';
say %hash{ [1,2,3].perl }; # displays 'foo'

.perl converts its argument to a string of Perl 6 code that's a literal version of that argument.

say [1,2,3].perl;    # displays '[1, 2, 3]'

Note how spaces have been added but that doesn't matter.

This isn't a perfect solution. You'll obviously get broken results if you mutate the array between key accesses. Less obviously you'll get broken results corresponding to any limitations or bugs in .perl:

say [my %foo{Array},42].perl; # displays '[(my Any %{Array}), 42]'

1 This is, hopefully, the end of my final final answer to your question. See my earlier 10th (!!) version of this answer for discussion of the alternative of using prefix ~ to achieve a more limited but similar effect and/or to try make some sense of my exchange with Liz in the comments below.

Community
  • 1
  • 1
raiph
  • 31,607
  • 3
  • 62
  • 111
  • I think the issues here is really: `(1,2,3)` is currently *not* a value type. I think a literal List should be. And that `($a,$b,$c)` would decont the variables as to also create a value type. Then the normal object hashes would do the right thing. – Elizabeth Mattijsen Apr 28 '18 at 13:31
  • A `Joiner` constant has the same vulnerability as `IterationEnd` has. It's visible and can therefore be abused. I was more thinking about a lexical `Mu.new` type of approach somehow, where the address of the object is the "secret". Not sure how that would work out though :-( – Elizabeth Mattijsen Apr 28 '18 at 17:46
  • Thanks. I like the idea of construction time **deep** (recursive) deconting of list literals, ensuring that a literal list is always frozen/immutable. Could also be an adverb (`:ro`?) on `List.new`. Also nice if the list is then marked so that introspection and the compiler can know it's immutable. That should significantly improve both the simplicity of writing definitively immutable code and a big class of compiler optimizations. – raiph Apr 29 '18 at 21:52