Perl 6: difference between Arrays and Lists

Perl 6 does support two different type of array-like iterables: [Array](https://docs.perl6.org/type/Array) and [List](https://docs.perl6.org/type/List). While both tend to have an uniform, iterable like behavior, they serve different scopes and in this article I would like to point out some of differences.

Arrays are arrays!

That’s a simple rule: if you use a @ sigil you are instrumenting the compiler to place an [Array](https://docs.perl6.org/type/Array) as container. An Array is a sequence of containers: an array does contains one container in every specific position, even when you construct multidimensional arrays. That’s said, the following piece of code is not surprising:
my @array = 'a' .. 'c';
@array.say;
say 'Container: ' ~ @array.VAR.^name;
say 'Name: '      ~ @array.^name;
producing as output
[a b c]
Container: Array
Name: Array
So what do we have here? A @ sigil provides an Array container. It’s so simple! There’s another simple rule: arrays are built depending on commas ,, not on parentheses. In other words, parentheses are used only to group commas into multidimensional arrays. The following example shows how @array stays mono-dimensional, while @m_array is not:
my @array = 'a', 'b', 'X', 'Y', 'Z';
@array.say;
say 'Container: ' ~ @array.VAR.^name;
say 'Name: '      ~ @array.^name;


my @m_array = ( 'a', 'b' ), ( 'X', 'Y', 'Z' );
@m_array.say;
say 'Container: ' ~ @m_array.VAR.^name;
say 'Name: '      ~ @m_array.^name;
that produces
[a b X Y Z]
Container: Array
Name: Array

[(a b) (X Y Z)]
Container: Array
Name: Array
As readers can see, the @m_array is really made up of two arrays, and it is even simpler to see this trying to iterate over the latter:
my @m_array = ( 'a', 'b' ), ( 'X', 'Y', 'Z' );
for @m_array -> @a {
    @a.say;
    say 'Container: ' ~ @a.VAR.^name;
    say 'Name: '      ~ @a.^name;
}
(a b)
Container: List
Name: List
(X Y Z)
Container: List
Name: List
and here comes the List: remember that every element of an array is a container, so a multidimensional array cannot be made by the union of literal arrays, but must be an array containing single containers that point to values. This is achieved by [List](https://docs.perl6.org/type/List).

Lists are scalar iterable containers

Well, [Lists](https://docs.perl6.org/type/List) are objects and therefore can be assigned to a Scalar container. Lists can be iterated and sliced as an Array, so they taste as arrays. In contrast to Arrays, the Lists can be lazy and can be assigned to a Scalar container. Let’s start with a simple example:
my @array    = 'a', 'b', 'X', 'Y', 'Z';
my @array_uc = @array.map( *.uc );
my $list     = ( @array, @array_uc );
$list.say;
say 'Container: ' ~ $list.VAR.^name;
say 'Name: '      ~ $list.^name;
the above code produces the following output:
([a b X Y Z] [A B X Y Z])
Container: Scalar
Name: List
As readers can see, the $list variable is of course a Scalar but holds a List and delegates to it. The List in turn, has been made by doubling the @array array, therefore it ends up holding a List made by two Arrays. This can be clearly seen iterating over the scalar (please note: iterating over a scalar):
for $list.list -> @a {
    @a.say;
    say 'Container: ' ~ @a.VAR.^name;
    say 'Name: '      ~ @a.^name;
}
that produces a very similar output for both iterations, expliciting saying that the List is made by two Arrays:
[a b X Y Z]
Container: Array
Name: Array

[A B X Y Z]
Container: Array
Name: Array
It is interesting to note that a slight change in the above code produces a totally different result:
my @array    = 'a', 'b', 'X', 'Y', 'Z';
# my @array_uc = @array.map( *.uc );
my $list     = ( @array, @array.map( *.uc ) );
$list.say;
say 'Container: ' ~ $list.VAR.^name;
say 'Name: '      ~ $list.^name;
with the adoption of map within the list construction the result is:
[a b X Y Z]
Container: Array
Name: Array

(A B X Y Z)
Container: List
Name: List
and that’s means that the second element of the $list is, in turn, a List. Usually this is not perceived at all, since Lists and Arrays are iterable in the very same way, as well as slicing works the same. Of course, being $list a scalar, it could seem awkward to write something like $list[0][1], but it does what it supposed to do (i.e., accessing the ‘b’), as well as $list[1][1] (i.e., accessing the ‘B’).

Lists are immutable, except when they are mutable!

In the previous example, $list[1] is a List made up by literals. What happens if one of those elements is modified by indexing? There’s an error:
$list[1][1] = 'C';
# Cannot modify an immutable Str (B)
but the same does not happen if an array element is changed on the fly. That is because, again, an Array forces a container on each element, while List does not. This is clearly shown introspecting the elements:
say "Element is $list[0][1] of type "
     ~ $list[0][1].^name ~ ' container is '
     ~ $list[0][1].VAR.^name;
say "Element is $list[1][1] of type "
     ~ $list[1][1].^name ~ ' container is '
     ~ $list[1][1].VAR.^name;
that produces as output:
Element is b of type Str container is Scalar
Element is B of type Str container is Str
So, for short, B is not modifiable, at least not directly. How to proceed then? A List element can be modified if it contains a container. Even the is copy on iteration will not produce the desired effect, consider the following:
for $list.list[1][0] -> $elem is copy  {
        $elem = 'CHANGED!' ~ $elem;
        say "Element is [$elem] of type "
            ~ $elem.^name ~ ' container is '
            ~ $elem.VAR.^name;
}

say "Element is $list[1][1] of type "
     ~ $list[1][1].^name ~ ' container is '
     ~ $list[1][1].VAR.^name;
what happens is that the $elem is changed within its scope, that is the for loop, but changes are not pushed back to the list. The only chance is to declare the list as made of containers:
my $first = 'a';
my $second = 'b';
$list = ( ( $first, $second ), @array );

say "Element is $list[1][1] of type " ~ $list[1][1].^name ~ ' container is ' ~ $list[1][1].VAR.^name;
$list[1][1] = 'C';
say "Element is $list[1][1] of type " ~ $list[1][1].^name ~ ' container is ' ~ $list[1][1].VAR.^name;
in this way the list is made by a first list with two Scalar containers within it, and that means it is possible to change them on the fly. So the rule of thumb is that the List itself is immutable (number of elements), but its values can be mutable as far as they are contained into a container.

Building a List one item at a time

Perl 6 prodives the special [gather take](https://docs.perl6.org/syntax/gather%20take) control flow to build up a list one element at a time. The idea is simple: each block of code that will produce a new item uses the take return-like statement, take instruments the otuer list to accept the new item as it gather. The following short example shows the concept in a clear way:
sub produce_one_element { take ('a' .. 'z').pick; }

my @list = gather produce_one_element  for 1..5;
@list.say;
# [c s q e w]
the @list is appended one element at a time via the gather control flow. Please consider that the following code will not achieve the same effect, since the for would be evaluated first and a single assignment is performed against the array:
sub produce_one_element { return ('a' .. 'z').pick; }

my @list =  produce_one_element  for 1..5;
@list.say;
# [m]
The gather take control flow allows also the lazy keyword for letting a list to be lazily populated. This is simply shown with the following code:
sub produce_five_elements {
    say 'Generating first element';
    take ('a' .. 'z').pick;
    say 'Generating second element';
    take ('a' .. 'z').pick;
    say 'Generating third element';
    take ('a' .. 'z').pick;
    say 'Generating fourth element';
    take ('a' .. 'z').pick;
    say 'Generating fifth element';
    take ('a' .. 'z').pick;
}

@list = lazy gather  produce_five_elements;

@list[0].say;
@list[4].say;
what happens is that the produce_five_elements will produce one letter each time it is called, yelding the outer scope control flow after each call. This means that once it has been defined the @list will be empty, and once it is accessed an element, several calls to satisfy such index will be performed. In other words, the output is the following:
Generating first element
o
Generating second element
Generating third element
Generating fourth element
Generating fifth element
w
As readers can see, after the first @list[0] access a single call to the method is performed, while after the access to the @list[4] all the previous items will be filled by a single method call. It is important to note that:
  1. each take must be performed to fill each index access, otherwise the Any instance will be placed in the case an index greater than the take-ing;
  2. the code block cannot be used without a gather, otherwise the compiler will respond with a take without gather.
The gather take control flow produces an Array container when assigned to a @ sigil variable, but produces a Seq when assigned to a Scalar.

Sequences

There is another special List case in Perl 6: [Seq](https://docs.perl6.org/type/Seq) (namely sequences). A sequence is a List not fully populated, so something that is lazily filled (e.g., via lazy gather take as shown above). A Seq does provide a partial iterable interface, and therefore cannot be bound to a @ variable:
my @seq = Seq.new( <1, 2, 3>  );
will produce an exception as expected Iterator but got List. In particular Seq does not keep around values once they have been accessed, so that the memory occupation does not grow linearly depending on the number of elements in the sequence. This is particular sueful when iterating thru the lines of a file.

Flattening arrays and lists

The particular [Slip](https://docs.perl6.org/type/Slip) class is a subclass of List and allows for the latter to become flat. A flat list is a list without a multidimensional structure. This means that using the | slip prefix operator the following @list will be made only by six elements, not two lists of three elements:
@list = ( |(1, 2, 3), |(9, 8, 7) );
@list.say;
# [1 2 3 9 8 7]
In the case of a gather take control flow it is required to use the slip() method to flat the elements returned into a flat list:
sub produce_three_elements {
    take slip (1, 2);
    take slip (4, 5);
    take slip (5, 6);
}

@list = gather produce_three_elements;
@list.say;
 # [1 2 4 5 5 6]
without the slip() method call on each take the result would have been three separated lists as in [(1 2) (4 5) (5 6)].

Emptying an Array

The special class Empty is a slip of an empty list, and assigning Empty to an array element makes it, well, empty! The same happens to assign the empty list () to a position:
my @array = 1,2,3,4;

@array[0] = 'FLUCA';
@array[1] = Empty;
@array[2] = ();
@array.say;
that produces [FLUCA () () 4]. Assigning Empty to the full array makes it totally empty, that is deletes all the elements. The same happens for List, with the execption that an empty list becomes immutable, while an empty array can be modified.

Conclusions

Perl 6 offers different iterable containers, mainly Array and List, the former are used as collection of containers for @ variables, while the latter is used for Scalar containers and could be lazy populated and immutable. In the case a List is lazily generated via a lazy gather take control flow, the List can mutate into a Seq. Arrays are mutable, since they force the containers on each element, while other itarable objects are immutable; it is still possible to change the content of a cell if it holds a container (Scalar). It is important to note that Seq does not implement the full iterable semantic, and therefore cannot be assigned to a @ variable.

The article Perl 6: difference between Arrays and Lists has been posted by Luca Ferrari on December 15, 2017