Iterators
Iterators provide an easy and comfortable way to implement custom sequences in your Silver project.
Iterators are special types of methods (func
s) 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.