Eugene Barsky
Eugene Barsky

Reputation: 5992

Hash with Array values in Perl 6

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

Upvotes: 5

Views: 279

Answers (2)

Jonathan Worthington
Jonathan Worthington

Reputation: 29454

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.

Upvotes: 12

raiph
raiph

Reputation: 32404

The issue is Scalar containers do DWIM indirection.

%a{3} is bound to a Scalar container.

By default, if you refer to the value or type of a Scalar container, you actually access the value, or type of the value, contained in the container.

In contrast, when you refer to an Array container as a single entity, you do indeed access that Array container, no sleight of hand.

To see what you're really dealing with, use .VAR which shows what a variable (or element of a composite variable) is bound to rather than allowing any container it's bound to to pretend it's not there.

say %a{3}.VAR ;       # $["foo", "bar", "baz"]
say %a{3}.Array.VAR ; # [foo bar baz]

This is a hurried explanation. I'm actually working on a post specifically focusing on containers.

Upvotes: 6

Related Questions