Pyro

A dynamically-typed, garbage-collected scripting language.

Version 0.19.2

Iterators

This tutorial demonstrates Pyro's iterator protocol by building an iterator for a custom linked-list type.



Pyro's for loop uses a simple iterator protocol for iterating over sequences of values. We we can illustrate this protocol by creating a custom iterator for a linked list.

First, we need a linked list.

The Linked List

Our linked list will contain a chain of nodes. Each node will contain a value and a reference to the next node in the chain.

class Node {
    pub var value;
    pub var next_node;

    def $init(value) {
        self.value = value;
    }
}

If the .next_node field is null, the node is the final node in the chain.

Next we'll create a wrapper List class that will let us add new nodes to the chain.

class List {
    var first_node;
    var last_node;

    pub def append(value) {
        var node = Node(value);

        if self.last_node == null {
            self.first_node = node;
            self.last_node = node;
            return;
        }

        self.last_node.next_node = node;
        self.last_node = node;
    }
}

We can use our List type like this:

var list = List();
list:append("foo");
list:append("bar");
list:append("baz");

Right now we can add nodes to the list but we have no way to access the values inside it. Let's fix that by making the list iterable.

The Iterator

An iterator is just an object with a :$next() method. This method should return either:

Here's an iterator class for our linked list:

class ListIterator {
    var next_node;

    def $init(first_node) {
        self.next_node = first_node;
    }

    def $next() {
        if self.next_node == null {
            return $err();
        }
        var value = self.next_node.value;
        self.next_node = self.next_node.next_node;
        return value;
    }
}

Making the List Iterable

A type is iterable if it has an :$iter() method that returns an iterator. We can make our List type iterable by adding the following method:

class List {
    ...

    def $iter() {
        return ListIterator(self.first_node);
    }

    ...
}

That's it, our List type is now iterable and we can use it in for loops, e.g.

var list = List();
list:append("foo");
list:append("bar");
list:append("baz");

for item in list {
    echo item;
}

This will print the output:

foo
bar
baz

Iterator Wrappers

Now that our List type is iterable, we can use it with Pyro's builtin iterator-wrapper type, iter.

An iterator wrapper, iter, is a builtin type that can wrap any iterator or iterable to automatically add support for a set of chainable, lazily-evaluated utility methods, e.g.

var vec = $iter(list):to_vec();
echo vec;

This will create a vector from our linked list, printing the output:

["foo", "bar", "baz"]

Iterator wrappers give our custom type automatic access to a set of powerful utility methods like :map() and :filter(), e.g.

var vec = $iter(list)
    :filter(def(value) {
        return value:starts_with("b");
    })
    :map(def(value) {
        return value:to_ascii_upper();
    })
    :to_vec();

echo vec;

This will create a vector containing filtered, uppercased values from the original list, printing the output:

["BAR", "BAZ"]