Hash with Array values in Perl 6

  • A+

What's going on here?

Why are %a{3} and %a{3}.Array different if %a has Array values and %a{3} is an Array?

> my Array %a {} > %a{3}.push("foo") [foo] > %a{3}.push("bar") [foo bar] > %a{3}.push("baz") [foo bar baz] > .say for %a{3} [foo bar baz] > %a{3}.WHAT (Array) > .say for %a{3}.Array foo bar baz 

The difference being observed here is the same as with:

my $a = [1,2,3]; .say for $a;        # [1 2 3] .say for $a.Array;  # 1/n2/n3/n 

The $ sigil can be thought of as meaning "a single item". Thus, when given to for, it will see that and say "aha, a single item" and run the loop once. This behavior is consistent across for and operators and routines. For example, here's the zip operator given arrays and them itemized arrays:

say [1, 2, 3] Z [4, 5, 6];    # ((1 4) (2 5) (3 6)) say $[1, 2, 3] Z $[4, 5, 6];  # (([1 2 3] [4 5 6])) 

By contrast, method calls and indexing operations will always be called on what is inside of the Scalar container. The call to .Array is actually a no-op since it's being called on an Array already, and its interesting work is actually in the act of the method call itself, which is unwrapping the Scalar container. The .WHAT is like a method call, and is telling you about what's inside of any Scalar container.

The values of an array and a hash are - by default - Scalar containers which in turn hold the value. However, the .WHAT used to look at the value was hiding that, since it is about what's inside the Scalar. By contrast, .perl [1] makes it clear that there's a single item:

my Array %a; %a{3}.push("foo"); %a{3}.push("bar"); say %a{3}.perl;      $["foo", "bar"] 

There are various ways to remove the itemization:

%a{3}.Array     # Identity minus the container %a{3}.list      # Also identity minus the container for Array @(%a{3})        # Short for %a{3}.cache, which is same as .list for Array %a{3}<>         # The most explicit solution, using the de-itemize op |%a{3}          # Short for `%a{3}.Slip`; actually makes a Slip 

I'd probably use for %a{3}<> { } in this case; it's both shorter than the method calls and makes clear that we're doing this purely to remove the itemization rather than a coercion.

While for |%a{3} { } also works fine and is visually nice, it is the only one that doesn't optimize down to simply removing something from its Scalar container, and instead makes an intermediate Slip object, which is liable to slow the iteration down a bit (though depending on how much work is being done by the loop, that could well be noise).

[1] Based on what I wrote, one may wonder why .perl can recover the fact that something was itemized. A method call $foo.bar is really doing something like $foo<>.^find_method('bar')($foo). Then, in a method bar() { self }, the self is bound to the thing the method was invoked on, removed from its container. However, it's possible to write method bar(/raw-self:) { } to recover it exactly as it was provided.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: