This page was generated by Text::SmartLinks v0.01 at 2019-04-19 11:48:06 GMT.
(syn 8d47115)
  [ Index of Synopses ]


Synopsis 7: Lists and Iteration


    Created: 07 September 2015
    Last Modified: 07 September 2015
    Version: 1

Design Overview

Perl 6 provides a wide array of list-related features, including eager, lazy, and parallel evaluation of sequences of values, and arrays offering compact and multi-dimensional storage. Laziness in particular needs careful handling, so as to provide the power advanced users desire while not creating surprises for typical language users who have the (reasonable) expectation that an assignment into an array will have immediate effect. Additionally, it is important to give the programmer control of when values will and won't be retained. Finally, all of this needs to be done in a way that provides the convenience that a Perl is expected to provide, while still having a model that can be understood through understanding a small number of rules.

Sequences vs. Lists

In Perl 6, we use the term "sequence" to refer to something that can, when requested, produce a sequence of values. Of note, it can only be asked to produce them once. We use the term "list" to refer to things that hold (and so remember) values. There are various concrete types that represent various kinds of list and sequence with different semantics:

    (1, 2, 3)       # a List, the simplest kind of list
    [1, 2, 3]       # an Array, a list of (assignable) Scalar containers
    |(1, 2)         # a Slip, a list which flattens into a surrounding List
    $*IN.lines      # a Seq, a sequence that can be processed serially
    (^1000).race    # a HyperSeq, a sequence that can be processed in parallel

The single argument rule

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

The first two are equivalent because parentheses do not actually construct a list, but only group. It is the infix:<,> operator that forms a list. The third also performs three iterations, since in Perl 6 [...] constructs an Array but does not wrap it into a Scalar container. The fourth will do two iterations, since the argument is a list of two things; that they both happen to have the @-sigil does not, alone, lead to any kind of flattening. The same goes for the fifth; infix:<,> will happily form a list of one thing.

The single argument rule does respect Scalar containers. Therefore:

    for $(1, 2, 3) { }      # List in a Scalar; 1 iteration
    for $[1, 2, 3] { }      # Array in a Scalar; 1 iteration
    for $@a { }             # Array in a Scalar; 1 iteration

The single argument rule is implemented consistently throughout the language. For example, consider the append method:

    @a.append: 1, 2, 3;       # appends 3 values to @a
    @a.append: [1, 2, 3];     # appends 3 values to @a
    @a.append: @b;            # appends @b.elems values to @a
    @a.append: @b,;           # same, trailing comma doesn't make > 1 argument
    @a.append: $(1, 2, 3);    # appends 1 value (a List) to @a
    @a.append: $[1, 2, 3];    # appends 1 value (an Array) to @a

Additionally, the list constructor (the infix:<,> operator) and the array composer (the [...] circumfix) follow the rule:

    [1, 2, 3]               # Array of 3 elements
    [@a, @b]                # Array of 2 elements
    [@a, 1..10]             # Array of 2 elements
    [@a]                    # Array with the elements of @a copied into it
    [1..10]                 # Array with 10 elements
    [$@a]                   # Array with 1 element (@a)
    [@a,]                   # Array with 1 element (@a)
    [[1]]                   # Same as [1]
    [[1],]                  # Array with a single element that is [1]
    [$[1]]                  # Array with a single element that is [1]

The only one of these that is likely to provide a surprise is [[1]], but it is deemed sufficiently rare that it does not warrant an exception to the very general single argument rule.

User-level Types


A List is an immutable, potentially infinite, list of values. The simplest way to form a List is with the infix:<,> operator:

    1, 2, 3

A List can be indexed and, provided it is finite, asked for its number of elements:

    say (1, 2, 3)[1];       # 2
    say (1, 2, 3).elems;    # 3

As it is immutable, it is not possible to push, pop, shift, unshift, or splice a List. The reverse and rotate operations return new Lists.

While a List itself is immutable, it may freely contain mutable things, including Scalar containers. Thus:

    my $a = 2;
    my $b = 4;
    ($a, $b)[0]++;
    ($a, $b)[1] *= 2;
    say $a;             # 3
    say $b;             # 8

Trying to assign to an immutable value in a List is an error, however.

    (1, 2, 3)[0]++;     # Dies: Cannot assign to an immutable value


The Slip type is a subclass of List. A Slip will have its values incorporated into a surrounding List.

    (1, (2, 3), 4).elems        # 3
    (1, slip(2, 3), 4).elems    # 4

It is possible to coerce a List to a Slip, so the above can also be written as:

    (1, (2, 3).Slip, 4).elems   # 4

This is a common way to get flattening in places it will not magically take place:

    my @a = 1, 2, 3;
    my @b = 4, 5;
    for @a.Slip, @b.Slip { }    # 5 iterations

It's also a bit verbose, which is why the prefix:<|> operator will, anywhere other than a function call argument list, do a Slip coercion:

    my @a = 1, 2, 3;
    my @b = 4, 5;
    for |@a, |@b { }            # 5 iterations

It can also be useful in forms such as:

    my @prefixed-values = 0, |@values;

Where the single argument rule would otherwise make @prefixed-values have two elements, the zero and @values.

The Slip type is also respected by map, gather/take, and lazy loops. It is the way a map can place multiple values into its result stream:

    my @a = 1, 2;
    say{ $_ xx 2 }).elems;      # 2
    say{ |($_ xx 2) }).elems;   # 4


An Array is a subclass of List that places values assigned to it into Scalar containers, meaning they can be mutated. It is the default type that an @-sigil variable gets.

    my @a = 1, 2, 3;
    say @a.WHAT;        # (Array)
    say @a;             # 1 3 3

In the absence of a shape specification, it will grow automatically.

    my @a;
    @a[5] = 42;
    say @a.elems;       # 6

An Array supports push, pop, shift, unshift, and splice.

Assignment to an array is eager by default, and creates a new set of Scalar containers:

    my @a = 1, 2, 3;
    my @b = @a;
    say @b;             # 1, 2, 3

Note that the [...] Array constructor is equivalent to creating and then assigning to an anonymous Array, and so has the same semantics with regard to eagerness and fresh containers.


A Seq is a one-shot producer of values. Most list processing operations return a Seq, as do most synchronous sources of multiple values.

    say (1, 2, 3).map(* + 1).^name;  # Seq
    say (1, 2 Z 'a', 'b').^name;     # Seq
    say (1, 1, * + * ... *).^name;   # Seq
    say $*IN.lines.^name;            # Seq

Since a Seq will not by default remember its values, it can only be consumed once. For example, if a Seq is stored:

    my \seq = (1, 2, 3).map(* + 1);

Then only one attempt to iterate it will work; subsequent attempts will die as the values have already been consumed:

    for seq { .say }    # 2\n3\n4\n
    for seq { .say }    # Dies: This Seq has already been iterated

This means you can be confident that a loop going over a file's lines:

    for open('data').lines {
        .say if /beer/;

Will not be retaining the lines of the file in memory. Additionally, it is easy to set up processing pipelines that also will not retain all of the lines in memory:

    my \lines = open('products').lines;
    my \beer = lines.grep(/beer/);
    my \excited =;
    .say for excited;

However, any attempt to re-use lines, beer, or excited will result in an error. This program is equivalent in performance to:

    .say for open('products').lines.grep(/beer/).map(&uc);

But provides a chance to name the stages. Note that it's possible to use Scalar variables instead, but the single argument rule means that the final loop would have to be:

    .say for |$excited;

Assigning a Seq to an Array will - so long as the sequence is not marked lazy - eagerly perform the operation and store the results into the Array. Therefore, there are no surprises to anyone writing:

    my @lines = open('products').lines;
    my @beer = @lines.grep(/beer/);
    my @excited =;
    .say for @excited;

Re-using any of these arrays will work out fine. Of course, the memory behavior of this program is radically different, and it will be slower due to all of the extra Scalar containers created (resulting in extra garbage collection) and poor locality of reference (we have to talk about the same string many times over the programs lifetime).

Occasionally it can be useful to request that a Seq cache itself. This can be done by calling the cache method on a Seq, which makes a lazy List from the Seq and returns it. Subsequent calls to cache will return the same lazy list. Note that the first call to cache counts as consuming the Seq, and so it will not work out if any prior iteration has taken place, and any later attempt to iterate the Seq after calling cache will also fail. It is only .cache which may be called more than once.

A Seq does not do the Positional role like a List. Therefore, it can not be bound to an @-sigil variable:

    my @lines := $*IN.lines;    # Dies

One consequence of this is that, naively, you could not pass a Seq as an argument to be bound to an @-sigil parameter:

    sub process(@data) {

This would be rather too inconvenient. Therefore, the signature binder (which actually uses ::= assignment semantics rather than :=) will spot failure to bind the <@>-sigil parameter, and then check if the argument does the PositionalBindFailover role. If it does, then it will call the cache method on the argument and bind the result of that instead.


Both Seq and List, along with various other types in Perl 6, do the Iterable role. The primary purpose of this role is to promise that an iterator method is available. The average Perl 6 user will rarely need to care about the iterator method and what it returns.

The secondary purpose of Iterable is to mark out things that will flatten on demand, when the flat method or function is used on them.

    my @a = 1, 2, 3;
    my @b = 4, 5;
    for flat @a, @b { }         # 5 iterations
    say [flat @a, @b].elems;    # 5

Another use of flat is to flatten nested List structure. For example, the Z (zip) operator produces a List of List:

    say (1, 2 Z 'a', 'b').perl; # ((1, "a"), (2, "b")).Seq

A flat can be used to flatten these, which is useful in conjunction with a for loop using a pointy block with multiple parameters:

    for flat 1, 2 Z 'a', 'b' -> $num, $letter { }

Note that flat respects Scalar containers, and so:

    for flat $(1, 2) { }

Will only do one iteration. Remember that an Array stores everything in a Scalar container, and so flat on an Array - short of weird tricks with binding - will always be the same as iterating over the Array itself. In fact, the flat method on an Array returns identity.



The Iterator API and Implementation Types

The Iterator role

The IterationBuffer class



    Jonathan Worthington <[email protected]>
[ Top ]   [ Index of Synopses ]