Iterating

Functionalities available for visiting all items in a complex data structure

The Iterator and Iterable roles

Raku is a functional language, but functions need something to hold on to when working on complex data structures. In particular, they need a uniform interface that can be applied to all data structures in the same way. One of these kind of interfaces is provided by the Iterator and Iterable roles.

The Iterable role is relatively simple. It provides a stub for the iterator method, which is the one actually used by statements such as for. for will call .iterator on the variable it precedes, and then run a block once for every item. Other methods, such as array assignment, will make the Iterable class behave in the same way.

class DNA does Iterable {
    has $.chain;
    method new ($chain where {
                       $chain ~~ /^^ <[ACGT]>+ $$ / and
                       $chain.chars %% 3 } ) {
        self.bless( :$chain );
    }

    method iterator(DNA:D:){ $.chain.comb.rotor(3).iterator }
};

my @longer-chain =  DNA.new('ACGTACGTT');
say @longer-chain.raku;
# OUTPUT: «[("A", "C", "G"), ("T", "A", "C"), ("G", "T", "T")]␤»

say  @longer-chain».join("").join("|"); # OUTPUT: «ACG|TAC|GTT␤»

In this example, which is an extension of the example in Iterable that shows how for calls .iterator, the iterator method will be called in the appropriate context only when the created object is assigned to a Positional variable, @longer-chain; this variable is an Array and we operate on it as such in the last example.

The (maybe a bit confusingly named) Iterator role is a bit more complex than Iterable. First, it provides a constant, IterationEnd. Then, it also provides a series of methods such as .pull-one, which allows for a finer operation of iteration in several contexts: adding or eliminating items, or skipping over them to access other items. In fact, the role provides a default implementation for all the other methods, so the only one that has to be defined is precisely pull-one, of which only a stub is provided by the role. While Iterable provides the high-level interface loops will be working with, Iterator provides the lower-level functions that will be called in every iteration of the loop. Let's extend the previous example with this role.

class DNA does Iterable does Iterator {
    has $.chain;
    has Int $!index = 0;

    method new ($chain where {
                       $chain ~~ /^^ <[ACGT]>+ $$ / and
                       $chain.chars %% 3 } ) {
        self.bless( :$chain );
    }

    method iterator( ){ self }
    method pull-one( --> Mu){
        if $!index < $.chain.chars {
            my $codon = $.chain.comb.rotor(3)[$!index div 3];
            $!index += 3;
            return $codon;
        } else {
            return IterationEnd;
        }
    }
};

my $a := DNA.new('GAATCC');
.say for $a; # OUTPUT: «(G A A)␤(T C C)␤»

We declare a DNA class which does the two roles, Iterator and Iterable; the class will include a string that will be constrained to have a length that is a multiple of 3 and composed only of ACGT.

Let us look at the pull-one method. This one is going to be called every time a new iteration occurs, so it must keep the state of the last one. An $.index attribute will hold that state across invocations; pull-one will check if the end of the chain has been reached and will return the IterationEnd constant provided by the role. Implementing this low-level interface, in fact, simplifies the implementation of the Iterable interface. Now the iterator will be the object itself, since we can call pull-one on it to access every member in turn; .iterator will thus return just self; this is possible since the object will be, at the same time, Iterable and Iterator.

This need not always be the case, and in most cases .iterator will have to build an iterator type to be returned (that will, for instance, keep track of the iteration state, which we are doing now in the main class), such as we did in the previous example; however, this example shows the minimal code needed to build a class that fulfills the iterator and iterable roles.

How to iterate: contextualizing and topic variables

for and other loops place the item produced in every iteration into the topic variable $_, or capture them into the variables that are declared along with the block. These variables can be directly used inside the loop, without needing to declare them, by using the ^ twigil.

Implicit iteration occurs when using the sequence operator.

say 1,1,1, { $^a²+2*$^b+$^c } … * > 300; # OUTPUT: «(1 1 1 4 7 16 46 127 475)

The generating block is being run once while the condition to finish the sequence, in this case the term being bigger than 300, is not met. This has the side effect of running a loop, but also creating a list that is output.

This can be done more systematically through the use of the gather/take blocks, which are a different kind of iterating construct that instead of running in sink context, returns an item every iteration. This Advent Calendar tutorial explains use cases for this kind of loops; in fact, gather is not so much a looping construct, but a statement prefix that collects the items produced by take and creates a list out of them.

Classic loops and why we do not like them

Classic for loops, with a loop variable being incremented, can be done in Raku through the loop keyword. Other repeat and while loops are also possible.

However, in general, they are discouraged. Raku is a functional and concurrent language; when coding in Raku, you should look at loops in a functional way: processing, one by one, the items produced by an iterator, that is, feeding an item to a block without any kind of secondary effects. This functional view allows also easy parallelization of the operation via the hyper or race auto-threading methods.

If you feel more comfortable with your good old loops, the language allows you to use them. However, it is considered better practice in Raku to try and use, whenever possible, functional and concurrent iterating constructs.

Note: Since version 6.d loops can produce a list of values from the values of last statements.

See Also

Classes and objects

A tutorial about creating and using classes in Raku

CompUnits and where to find them

How and when Raku modules are compiled, where they are stored, and how to access them in compiled form.

Concurrency

Concurrency and asynchronous programming

Command line interface

Creating your own CLI in Raku

Grammar tutorial

An introduction to grammars

Input/Output

File-related operations

Inter-process communication

Programs running other programs and communicating with them

Doing math with Raku

Different mathematical paradigms and how they are implemented in this language

Module packages

Creating module packages for code reuse

Core modules

Core modules that may be useful to module authors

Module development utilities

What can help you write/test/improve your module(s)

Modules

How to create, use, and distribute Raku modules

Creating operators

A short tutorial on how to declare operators and create new ones.

Regexes: best practices and gotchas

Some tips on regexes and grammars

REPL

Read-eval-print loop

Entering unicode characters

Input methods for unicode characters in terminals, the shell, and editors

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.