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:
- the next value from a sequence, or
-
an
err
if the sequence has been exhausted
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"]