Iterators

Iterators provide an easy and comfortable way to implement custom sequences in your Silver project.

Iterators are special types of methods (funcs) that return a sequence. When called, the body of the iterator method is not actually executed, but a new object is instantiated, representing the sequence. Once code starts to enumerate over the sequence, for example in a for in loop, the iterator's method body will be executed piece by piece in order to provide the sequence elements.

A sample, as the saying goes, says more than a thousand words, so let's start by looking at a real live iterator implementation:

func getEvenNumbers() -> ISequence<Int> {
  for var i: Int = 0; i < 100; i++ {
    __yield i
}

That's a fairly simple iterator method that will provide a sequence of all even numbers between 0 and 100. There are two things that make this method special:

First, the method return type is an ISequence<T> protocol type.

Second, you will notice the use of the __yield keyword inside the for loop. This keyword is specific to iterator implementations, and used to pass back the individual elements of the sequence. You can think of this as the equivalent of return i, but instead of exiting the method, the value of i will be returned as part of the sequence, and the iterator will continue its work.

This is a lot to take in on first sight, so let's walk through what happens when this iterator is used in code such as this:

let numbers := getEvenNumbers()
for n in numbers {
  println(n)
}

The first line calls our iterator method, but instead of executing any of the code we wrote in getEvenNumbers, this will simply create a new sequence object and pass it back, storing it in the numbers variable. At this point, none of the "hard work" needed to calculate the sequence will be performed yet.

Next, the for in loop runs over the sequence, which in turn starts executing the iterator body. The for loop starts at 0, and reaches the yield keyword. At this point, the iterator will halt, and the first value, 0 will be passed back as the first element of the sequence. The body of the for in loop in the second snippet now executes with n = 0 and writes that value out to the console.

As the for each loop resumes, it will try to get the next value of the sequence – which in turn resumes our iterator body. The for loop continues to i = 2, and the game continues.

Eventually, the iterator will reach 100, and exit – the end of the sequence has been reached, and the for each loop too will terminate as well.

As you can see in this simple example, iterators can make it very easy to implement complex sequences by allowing the entire buildup of the sequence to be written as normal sequential code. Beyond what is shown in this sample, iterators can contain __yield statements in various places, contain nested loops, conditions and almost all of Swift's language constructs that you can use in ordinary methods. This allows them to perform operations that would be very complex to achieve if every iteration of the sequence would need to be encapsulated independently.

Delegating Iteration to a Sequence

The yield keyword also supports delegation of the iterator to a second sequence, as shown below:

var moreValues = [3,4,5,6] // array of Int, can act as sequence

func myIterator() -> ISequence<Int> {
  __yield 1 // adds "1" to the sequence
  __yield 2 // adds "2" to the sequence
  __yield moreValues // adds "3" thru "6" to the sequence
  __yield 7 // adds "7" to the sequence
}

Limitations inside Iterator Methods

Since yielding in an iterator returns back to the caller, it's not possible to __yield from within a protected block such as a __try/ __finally block, as there is no guarantee that the caller will actually finish looping over the sequence.

See Also