Naturally lazy
D:\>6e "my #bar = 'a', 'b', 'c'; sub foo( #b ) { my $bar = 0; gather loop { print "*"; take ($bar, #b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( #bar )"
*(0 a)*(1 b)*(2 c)
So far, so expected. Now, let's be eager.
D:\>6e "my #bar = 'a', 'b', 'c'; sub foo( #b ) { my $bar = 0; eager gather loop { print "*"; take ($bar, #b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( #bar )"
***(3 a)(3 b)(3 c)
Why on earth are the numeric values all "3"? As if, I don't know, the closure vanished in a puff of illogic. take-rw doesn't cut it, either.
D:\>6e "my #bar = 'a', 'b', 'c'; sub foo( #b ) { my $bar = 0; eager gather loop { print "*"; take-rw ($bar, #b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( #bar )"
***(3 a)(3 b)(3 c)
I can mitigate
D:\>6e "my #bar = 'a', 'b', 'c'; sub foo( #b ) { my $bar = 0; eager gather loop { print "*"; take-rw ($bar + 0, #b[$bar]); $bar++; last if $bar > 2; } }; .print for foo( #bar )"
***(0 a)(1 b)(2 c)
But why do I have to?
Edit: So, questions boils down to,
>6e "my $bar = 0; .say for gather { take ($bar,); $bar++; take ($bar,) }"
(0)
(1)
>6e "my $bar = 0; .say for eager gather { take ($bar,); $bar++; take ($bar,) }"
(1)
(1)
It is still unexplained, why there is a difference in behaviour in eager vs. lazy case.
Holli has accepted the answer that quoted two of Brad's comments. I deliberately kept that one terse.
But you're reading this.1 So I'll go in the opposite direction with this answer.
($bar,...) is a list containing $bar, not $bar's value
The behavior shown in the question really is all about containers vs values, and how list literals store containers, not their values1:
my $bar = 0;
my $list = ($bar,);
say $list[0]; # 0
$bar = 1;
say $list[0]; # 1
The laziness vs eager aspect is just things doing what they're all supposed to do. Emphasizing this first point may be enough to inspire you to focus on containers vs values. And maybe that'll lead you to quickly understand what went wrong in Holli's code. But maybe not.1 So I'll continue.
What happens in the lazy case?
A lazy list waits until a value is demanded before attempting to produce it. Then, it just does the necessary work and pauses, yielding control, until it's asked to produce another value at some later time.
In Holli's code the for loop demands values.
The first time around the for loop, it demands a value from the lazy expression. This turns around and demands a value from the gather'd expression. The latter then computes until the take, by which time it's created a list whose first element is the container $bar. This list is the result of the take.
Then the .print prints that first list. At the moment of printing, $bar still contains 0. (The first increment of $bar hasn't yet happened.)
The second time around the for loop, the inner control structure enclosing the take (the loop) is re-entered. The first thing that happens is that $bar gets incremented for the first time. Then the loop exit condition is checked (and fails), so the second time around the loop starts. Another list is created. Then it's taked.
When the second list is printed, its first element, which is the $bar container, prints as 1, not 0, because at that point, having been incremented, $bar now contains 1.
(If Holli had written code that held onto the first list, and printed that first list again now, after having just printed the second list, they'd have discovered that the first list also now printed with a 1, no longer a 0. Because all the taked lists have the same $bar container as their first element.)
And likewise the third list.
After the third list has been printed, the for loop demands a fourth go at the gather. This re-enters the loop at the statement after the take statement. $bar gets incremented for the third time, to 3, and then the last if $bar > 2; condition triggers, exiting the loop (and thus the expression being gather'd and ultimately the whole .print for ... statement).
What happens in the eager case?
All the gathering is completed before any of the printing.
At the end of this, the for construct has a sequence of three lists. It has not yet called any .print calls. The third time around the loop in the gather has left $bar containing 3.
Next, .print is called on each of the three lists. $bar contains 3 so they all print with 3 as their first element.
Solving the problem by switching to an array
I think the idiomatic way to deal with this would be to switch from a list literal to an array literal:
[$bar, #bar[$bar]]
# instead of
($bar, #bar[$bar])
This works because, unlike a list literal, an array literal treats an element that's a container as an r-value2, i.e. it copies the value contained in the container out of that container, rather than storing the container itself.
It just so happens that the value is copied into another new Scalar container. (That's because all elements of new non-native arrays are fresh Scalar containers; this one of the main things that makes an array different from a list.) But the effect in this context is the same as if the value were copied directly into the array because it no longer matters that the value contained in $bar is changing as things proceed.
The upshot is that the first element of the three arrays ends up containing, respectively, 0, 1, and 2, the three values that were contained in $bar at the time each array was instantiated.
Solving the problem by switching to an expression
As Holli noted, writing $bar + 0 also worked.
In fact any expression will do, so long as it isn't just $bar on its own.
Of course, the expression needs to work, and return the right value. I think $bar.self should work and return the right value no matter what value $bar is bound to or assigned.
(Though it does read a little strangely; $bar.self is not $bar itself if $bar is bound to a Scalar container! Indeed, in an even more counter-intuitive twist, even $bar.VAR, which uses .VAR, a method which "Returns the underlying Scalar object, if there is one.", still ends up being treated as an r-value instead!)
Does the doc need updating?
The above is an entirely logical consequence of:
What Scalars are;
What list literals do with Scalars;
What lazy vs eager processing means.
If the doc is weak, it's presumably in its explanation of one of the last two aspects. It looks like it's primarily the list literal aspect.
The doc's Syntax page has a section on various literals, including Array literals, but not list literals. The doc's Lists, sequences, and arrays does have a List literals section (and not one on Arrays) but it doesn't mention what they do with Scalars.
Presumably that warrants attention.
The Lists, sequences, and arrays page also has a Lazy lists section that could perhaps be updated.
Putting the above together it looks like the simplest doc fix might be to update the Lists, sequences, and arrays page.
Footnotes
1 In my first couple versions of this answer (1, 2, I tried to get Holli to reflect on the impact of containers vs values. But that failed for them and maybe hasn't worked for you either. If you're not familiar with Raku's containers, consider reading:
Containers, the official doc's "low-level explanation of Raku containers".
Containers in Perl 6, the third of Elizabeth Mattijsen's series of articles about Raku fundamentals for those familiar with Perl.
2 Some of the details in Wikipedia's discussion of "l-values and r-values" don't fit Raku but the general principle is the same.
In Raku most values are immutable.
my $v := 2;
$v = 3;
Cannot assign to an immutable value
To make variables, you know actually variable, there is a thing called a Scalar.
By default $ variables are actually bound to a new Scalar container.
(Which makes sense since they are called scalar variables.)
my $v;
say $v.VAR.^name; # «Scalar»
# this doesn't have a container:
my $b := 3;
say $v.VAR.^name; # «Int»
You can pass around this Scalar container.
my $v;
my $alias := $v;
$alias = 3;
say $v; # «3»
my $v;
sub foo ( $alias is rw ){ $alias = 3 }
foo($v);
say $v; # «3»
You can even pass it around in a list.
my $v;
my $list = ( $v, );
$list[0] = 3;
say $v; # «3»
This is the basis of the behaviour that you are seeing.
The thing is that you don't actually want to pass around the container, you want to pass around the value in the container.
So what you do is decontainerize it.
There are a few options for this.
$v.self
$v<>
$v[]
$v{}
Those last few only make sense on Array or Hash containers, but they also work on Scalar containers.
I would recommend using $v.self or $v<> for decontainerization.
(Technically $v<> is short for $v{qw<>} so it is for Hash containers, but there seems to be a consensus that $v<> can be used generally for this purpose.)
When you do $v + 0 what you are doing is creating a new value object that is not in a Scalar container.
Since it is not in a container, the value itself gets passed instead of a container.
You don't notice this happening in the lazy case, but it is still happening.
It's just that you have printed the current value in the container before it gets changed out from underneath you.
my #bar = 'a', 'b', 'c';
sub foo( #b ) {
my $bar = 0;
gather loop {
print "*";
take ($bar, #b[$bar]);
$bar++;
last if $bar > 2;
}
};
my \sequence = foo( #bar ).cache; # cache so we can iterate more than once
# there doesn't seem to be a problem the first time through
.print for sequence; # «*0 a*1 b*2 c»
# but on the second time through the problem becomes apparent
.print for sequence; # «3 a3 b3 c»
sequence[0][0] = 42;
.print for sequence; # «42 a42 b42 c»
say sequence[0][0].VAR.name; # «$bar»
# see, we actually have the Scalar container for `$bar`
You would also notice this if you used something like .rotor on the resulting sequence.
.put for foo( #bar ).rotor(2 => -1);
# **1 a 1 b
# *2 b 2 c
It's still just as lazy, but the sequence generator needs to create two values before you print it the first time. So you end up printing what is in $bar after the second take.
And in the second put you print what was is in it after the third take.
Specifically notice that the number associated with the b changed from a 1 to a 2.
(I used put instead of print to split up the results. It would have the same behaviour with print.)
If you decontainerize, you get the value rather than the container.
So then it doesn't matter what happens to $bar in the meantime.
my #bar = 'a', 'b', 'c';
sub foo( #b ) {
my $bar = 0;
gather loop {
print "*";
take ($bar.self, #b[$bar]); # <------
$bar++;
last if $bar > 2;
}
};
my \sequence = foo( #bar ).cache;
.print for sequence; # «*0 a*1 b*2 c»
.print for sequence; # «0 a1 b2 c»
# sequence[0][0] = 42; # Cannot modify an immutable List
# say sequence[0][0].VAR.name; # No such method 'name' for invocant of type 'Int'.
.put for foo( #bar ).rotor(2 => -1);
# **0 a 1 b
# *1 b 2 c
Per #BradGilbert:
It is only about containers and values.
The reason the lazy case works is because you don't notice that the old values changed out from under you, because they are already printed.
See Brad's and raiph's answers for further discussion.
Related
What is it the correct syntax to assign a Seq(Seq) into multiple typed arrays without assign the Seq to an scalar first? Has the Seq to be flattened somehow? This fails:
class A { has Int $.r }
my A (#ra1, #ra2);
#create two arrays with 5 random numbers below a certain limit
#Fails: Type check failed in assignment to #ra1; expected A but got Seq($((A.new(r => 3), A.n...)
(#ra1, #ra2) =
<10 20>.map( -> $up_limit {
(^5).map({A.new( r => (^$up_limit).pick ) })
});
TL;DR Binding is faster than assignment, so perhaps this is the best practice solution to your problem:
:(#ra1, #ra2) := <10 20>.map(...);
While uglier than the solution in the accepted answer, this is algorithmically faster because binding is O(1) in contrast to assignment's O(N) in the length of the list(s) being bound.
Assigning / copying
Simplifying, your non-working code is:
(#listvar1, #listvar2) = list1, list2;
In Raku infix = means assignment / copying from the right of the = into one or more of the container variables on the left of the =.
If a variable on the left is bound to a Scalar container, then it will assign one of the values on the right. Then the assignment process starts over with the next container variable on the left and the next value on the right.
If a variable on the left is bound to an Array container, then it uses up all remaining values on the right. So your first array variable receives both list1 and list2. This is not what you want.
Simplifying, here's Christoph's answer:
#listvar1, #listvar2 Z= list1, list2;
Putting the = aside for a moment, Z is an infix version of the zip routine. It's like (a physical zip pairing up consecutive arguments on its left and right. When used with an operator it applies that operator to the pair. So you can read the above Z= as:
#listvar1 = list1;
#listvar2 = list2;
Job done?
Assignment into Array containers entails:
Individually copying as many individual items as there are in each list into the containers. (In the code in your example list1 and list2 contain 5 elements each, so there would be 10 copying operations in total.)
Forcing the containers to resize as necessary to accommodate the items.
Doubling up the memory used by the items (the original list elements and the duplicates copied into the Array elements).
Checking that the type of each item matches the element type constraint.
Assignment is in general much slower and more memory intensive than binding...
Binding
:(#listvar1, #listvar2) := list1, list2;
The := operator binds whatever's on its left to the arguments on its right.
If there's a single variable on the left then things are especially simple. After binding, the variable now refers precisely to what's on the right. (This is especially simple and fast -- a quick type check and it's done.)
But that's not so in our case.
Binding also accepts a standalone signature literal on its left. The :(...) in my answer is a standalone Signature literal.
(Signatures are typically attached to a routine without the colon prefix. For example, in sub foo (#var1, #var2) {} the (#var1, #var2) part is a signature attached to the routine foo. But as you can see, one can write a signature separately and let Raku know it's a signature by prefixing a pair of parens with a colon. A key difference is that any variables listed in the signature must have already been declared.)
When there's a signature literal on the left then binding happens according to the same logic as binding arguments in routine calls to a receiving routine's signature.
So the net result is that the variables get the values they'd have inside this sub:
sub foo (#listvar1, #listvar2) { }
foo list1, list2;
which is to say the effect is the same as:
#listvar1 := list1;
#listvar2 := list2;
Again, as with Christoph's answer, job done.
But this way we'll have avoided assignment overhead.
Not entirely sure if it's by design, but what seems to happen is that both of your sequences are getting stored into #ra1, while #ra2 remains empty. This violates the type constraint.
What does work is
#ra1, #ra2 Z= <10 20>.map(...);
There are both placeholder variables and topic variables in Perl 6. For example, the following two statements are the same
say ( $_ * 2 for 3, 9 ); # use topic variables
say ( { $^i * 2 } for 3, 9 ); # use placeholder variables
It seems to me, the only benefit one gets from topic variables is saving some keyboard strokes.
My question is: Is there a use case, where a topic variable can be much more convenient than placeholder variables?
The topic can have method calls on it:
say ( .rand for 3,9);
Compared to a placeholder:
say ( {$^i.rand} for 3,9);
Saves on typing a variable name and the curly braces for the block.
Also the topic variable is the whole point of the given block to my understanding:
my #anArrayWithALongName=[1,2,3];
#anArrayWithALongName[1].say;
#anArrayWithALongName[1].sqrt;
#OR
given #anArrayWithALongName[1] {
.say;
.sqrt;
}
That's a lot less typing when there are a lot of operations on the same variable.
There are several topic variables, one for each sigil: $, #, %_ and even &_ (yep, routines are first-class citizens in Perl6). To a certain point, you can also use Whatever (*) and create a WhateverCode in a expression, saving even more typing (look, ma! No curly braces!).
You can use the array form for several variables:
my &block = { sum #_ }; say block( 2,3 )
But the main problem they have is that they are single variables, unable to reflect the complexity of block calls. The code above can be rewritten using placeholder variables like this:
my &block = { $^a + $^b }; say block( 2,3 )
But imagine you've got some non-commutative thing in your hands. Like here:
my &block = { #_[1] %% #_[0] }; say block( 3, 9 )
That becomes clumsy and less expressive than
my &block = { $^divi %% $^divd }; say block( 3, 9 ); # OUTPUT: «True»
The trick being here that the placeholder variables get assigned in alphabetical order, with divd being before divi, and divi being short for divisible and divd for divided (which you chould have used if you wanted).
At the end of the day, there are many ways to do it. You can use whichever you want.
Assigning an iterator to variable changes apparently how the Seq behaves. E.g.
use v6;
my $i = '/etc/lsb-release'.IO.lines;
say $i.WHAT;
say '/etc/lsb-release'.IO.lines.WHAT;
.say for $i;
.say for '/etc/lsb-release'.IO.lines;
results in:
(Seq)
(Seq)
(DISTRIB_ID=Ubuntu DISTRIB_RELEASE=18.04 DISTRIB_CODENAME=bionic DISTRIB_DESCRIPTION="Ubuntu 18.04.1 LTS")
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.1 LTS"
So once assigned I get only the string representation of the sequence. I know I can use .say for $i.lines to get the same output but I do not understand the difference between the assigned and unassigned iterator/Seq.
Assignment in Perl 6 is always about putting something into something else.
Assignment into a Scalar ($ sigil) stores the thing being assigned into a Scalar container object, meaning it will be treated as a single item; this is why for $item { } will not do an iteration. There are various ways to overcome this; the most conceptually simple way is to use the <> postfix operator, which strips away any Scalar container:
my $i = '/etc/lsb-release'.IO.lines;
.say for $i<>;
There's also the slip operator ("flatten into"), which will achieve the same:
my $i = '/etc/lsb-release'.IO.lines;
.say for |$i;
Assignment into an Array will - unless the right-hand side is marked lazy - iterate it and store each element into the Array. Thus:
my #i = '/etc/lsb-release'.IO.lines;
.say for #i;
Will work, but it will eagerly read all the lines into #i before the loop starts. This is OK for a small file, but less ideal for a large file, where we might prefer to work lazily (that is, only pulling a bit of the file into memory at a time). One might try:
my #i = lazy '/etc/lsb-release'.IO.lines;
.say for #i;
But that won't help with the retention problem; it just means the array will be populated lazily from the file as the iteration takes place. Of course, sometimes we might want to go through the lines multiple times, in which case assignment into an Array would be the best choice.
By contrast, declaring a symbol and binding it to that:
my \i = '/etc/lsb-release'.IO.lines;
.say for i;
Is not a "put into" operation at all; it just makes the symbol i refer to exactly what lines returns. This is rather clearer than putting it into a Scalar container only to take it out again. It's also a little easier on the reader, since a my \foo = ... can never be rebound, and so the reader doesn't need to be on the lookup for any potential changes later on in the code.
As a final note, it's worth knowing that the my \foo = ... form is actually a binding, rather than an assignment. Perl 6 allows us to write it with the = operator rather than forcing :=, even if in this case the semantics are := semantics. This is just one of a number of cases where a declaration with an initializer differs a bit from a normal assignment, e.g. has $!foo = rand actually runs the assignment on every object instantiation, while state $foo = rand only runs it only if we're on the first entry to the current closure clone.
If you want to be able to iterate over the sequence you need to either assign it to a positional :
my #i = '/etc/lsb-release'.IO.lines;
.say for #i;
Or you can tell the iterator that you want to treat the given thing as iterable :
.say for #$i
Or you can Slip it into a list for the iterator :
.say for |$i
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.
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.
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
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.
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