14

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?

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
piojo
  • 6,351
  • 1
  • 26
  • 36

3 Answers3

11

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
Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • The "consistency" you refer to means both array constructors follow the single argument rule? What I'm asking is why ANY array constructor follows the single argument rule. Clearly `Hash` has to flatten its arguments when an argument is uneven, but why `Array`? – piojo Jan 24 '18 at 12:19
  • I think ultimately only TimToady can answer that. – Elizabeth Mattijsen Jan 24 '18 at 12:36
  • 2
    @piojo I think part of it was people writing `my @array = [ 1,2,3 ];` and not expecting `@array` to be equivalent with `[[1,2,3],]`. That is `my @array = [ %hash ];` applies the single arg rule twice. – Brad Gilbert Jan 24 '18 at 16:50
  • 1
    @BradGilbert That is sensible. But it comes down to a problem with Perl 6's core syntax. IMO that should have been solved by changing the function of the comma operator to be more similar to other programming languages, and perhaps adding an implicit list constructor only when the comma is not wrapped in another operator and is not part of a function invocation. Too late to change now. I just don't think the single argument rule is a good fit for list-like structures. – piojo Jan 25 '18 at 01:23
  • @piojo "changing the function of the comma operator to be more similar to other programming languages" Fwiw, afaict Python uses the same rules regarding comma. "I just don't think the single argument rule is a good fit for list-like structures." Fwiw I think it's pretty much ideal and I've not been able to figure out from your question and comments how it could be "nearly impossible" to "write out a lot of data when using MongoDB, since MongoDB API arguments must be formatted as nested lists/arrays.". Perhaps add an example of what you mean to your question? – raiph Jan 25 '18 at 02:45
  • 1
    @raiph It seems Python has taken a similar course and special-cased what the , operator does when it's within `[]`, to avoid unintentional duplicate levels of nesting. I was also thinking that could have been a solution for Perl 6, but wasn't able to decide whether there are other cases this change would need to be applied. – piojo Jan 25 '18 at 03:24
  • 1
    @piojo "Python has ... special-cased what the `,` operator does when it's within `[]`". I see what Python does as the opposite of that -- special-casing what a *lack* of a `,` operator within `[]` does compared to what lack of a `,` in `()` does. "to avoid unintentional duplicate levels of nesting". I see what Python does as the opposite of that -- avoiding unintentionally *not* duplicating nesting. – raiph Jan 27 '18 at 17:03
  • 1
    @MustafaAydın Indeed! Thanks for the spot, fixed now. – Elizabeth Mattijsen Jul 24 '21 at 09:39
9

Motivation for the array construction operator using the 1-arg rule:

  • Because it's really just a circumfix operator, not special syntax.
    Other operators/keywords that expect a potentially-nested list as input, use the single-argument rule - so for consistency, this one does too.

Motivation for list-transforming operators/keywords in general using the 1-arg rule:

  • To facilitate the common case where the whole input list is already stored in one variable.
  • To avoid further special-casing literal commas.
    Operators don't have argument lists like functions; they accept just one object as their argument (unary/circumfix operators), or one on each side (binary operators). The expression @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).
  • The fact that the argument doesn't get iterated when it is wrapped in an item container (i.e. a $ 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

What about subroutines/methods?

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 Lists – 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.

smls
  • 5,738
  • 24
  • 29
8

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

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
librasteve
  • 6,832
  • 8
  • 30