Given a single argument, the Array
constructor flattens it. This causes problems:
my %hash = (a => 1; b => 2);
my @array = [ %hash ]; # result: [a => 1 b => 2], expected [{ a => 1, b => 2 }]
The List constructor doesn't have this quirk (the single-argument rule), but unfortunately there's no short syntax for creating a one-element list:
List.new(%hash); # result is ({a => 1, b => 2}), as expected
Workaround: if your argument is a scalar, it won't auto-flatten:
my $hash = %hash;
my @array = [ $%hash ]; # or my @array = [ $%hash ], or just my @array = $%hash
# result: [{a => 1, b => 2}], as expected
Another workaround is to add a comma at the end of the element list:
my @array = [ %hash, ];
The real problem is when we write out data literally. Representing a JSON-like nested structure in Perl 6 is a real problem if 1-element lists are flattened, but other lists are not. The data ends up being wrong. I had to write out a lot of data when using MongoDB, since MongoDB API arguments must be formatted as nested lists/arrays. It was nearly impossible. So I ask, what is the motivation for flattening a single array element?
The motivation for flattening a single array element, is to consistently apply the single argument rule. The array constructor [ ]
also follows the single argument rule. Maybe it helps to think of [%h]
as circumfix:<[ ]>(%h)
, which it actually is. If you do not want flattening, you can either itemize it (prefix a $
), or, as you've shown, add a comma to make it a List
. This then follows the same logic as ($a)
being the same as $a
, but ($a,)
being a List
with one element $a
.
my %h = a => 42, b => 666;
dd [%h]; # [:a(42), :b(666)]
dd [%h,%h]; # [{:a(42), :b(666)}, {:a(42), :b(666)}]
dd [%h,] # [{:a(42), :b(666)},] make it a List first
dd [$%h] # [{:a(42), :b(666)},] itemize the Hash
@a,
constructs a one-element List
, so when passing the result of that expression to a list-transforming operator, it treats that List
just like it would treat any other non-containerized Iterable
passed to it: It iterates it, and operates on its element(s).$
variable), is meant to preserve the singular-plural distinction of the sigils, as @p6steve's answer explains.Here's the aforementioned consistency, demonstrated using one keyword, one binary operator, and one circumfix operator:
for @a { ... } # n iterations
for @a, { ... } # 1 iteration
for @a, @b { ... } # 2 iterations
for $a { ... } # 1 iteration
1..Inf Z @a # new Seq of n elements
1..Inf Z @a, # new Seq of 1 element
1..Inf Z @a, @b # new Seq of 2 elements
1..Inf Z $a # new Seq of 1 element
[ @a ] # new Array with n elements
[ @a, ] # new Array with 1 element
[ @a, @b ] # new Array with 2 elements
[ $a ] # new Array with 1 element
These do have argument lists, so the single-argument rule doesn't come as naturally to them as it does to operators.
Note that in the top-level scope of an argument list, commas don't create List
s – they separate arguments.
Subroutines/methods that are considered "list transformation" routines which expect potentially-nested lists as input, still participate in the single-arument rule though, by checking whether they got one argument or multiple, and if just one, whether it is wrapped in an item container:
map {...}, @a; # n iterations
map {...}, (@a,); # 1 iteration (Parens needed to get a List-constructing comma.)
map {...}, @a, @b; # 2 iterations
map {...}, $a; # 1 iteration
User-defined routines can easily get this behavior by using a +@
signature.
The @ sigil in Perl indicates "these", while $ indicates "the". This kind of plural/single distinction shows up in various places in the language, and much convenience in Perl comes from it. Flattening is the idea that an @-like thing will, in certain contexts, have its values automatically incorporated into the surrounding list. Traditionally this has been a source of both great power and great confusion in Perl. Perl 6 has been through a number of models relating to flattening during its evolution, before settling on a straightforward one known as the "single argument rule".
The single argument rule is best understood by considering the number of iterations that a for loop will do. The thing to iterate over is always treated as a single argument to the for loop, thus the name of the rule.
for 1, 2, 3 { } # List of 3 things; 3 iterations
for (1, 2, 3) { } # List of 3 things; 3 iterations
for [1, 2, 3] { } # Array of 3 things (put in Scalars); 3 iterations
for @a, @b { } # List of 2 things; 2 iterations
for (@a,) { } # List of 1 thing; 1 iteration
for (@a) { } # List of @a.elems things; @a.elems iterations
for @a { } # List of @a.elems things; @a.elems iterations
from Synopsis7 https://design.raku.org/S07.html#The_single_argument_rule
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