Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a sensible reason why the Perl 6 array constructor flattens its argument?

Tags:

raku

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?

like image 887
piojo Avatar asked Jan 24 '18 05:01

piojo


3 Answers

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
like image 50
Elizabeth Mattijsen Avatar answered Jan 13 '23 04:01

Elizabeth Mattijsen


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.

like image 35
smls Avatar answered Jan 13 '23 04:01

smls


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

like image 36
p6steve Avatar answered Jan 13 '23 04:01

p6steve