Swift: Use for-in loops with your own sequence types


For-in loop

The for-in loop might well be familiar to you from working with arrays. For example:
let intArray = [1,2,3,4,5,6]
   for int in intArray {
   println(int)
}
Here each value in the array is iterated over and returned as the value (in this case named int) within the loop.

Build your own sequence and generator

We can use the for-in loop to iterate over our own types that adopt the SequenceType protocol as is explored in this post.

There are two requirements that the SequenceType protocol makes of a Type that adopts it: (1) the type must contain a method to return an instance of a Type that adopts the GeneratorType protocol, (2) it must typealias the name of the generator type to GeneratorType. And these requirements in their barest form might look like this:
struct MySequence:SequenceType {
    typealias GeneratorType = MyGenerator
    func generate() -> GeneratorType {
    var gen = MyGenerator()
    return gen
    }
}
Next, because we are instantiating a generator (as we always must with a sequence), we need the accompanying generator code:
struct MyGenerator:GeneratorType {
    typealias Element = Bool
    func next() -> Element? {
        return nil
    }
}
Here we must identify the return Element type and then return it, or as in this case return nil, since the element as should be note is an optional. This is very important for the for-in loop because the returning of nil instructs it when to end the loop.

Working with for-in

The next step is to do something useful with our sequence and generator pairing. So let's decide to do something like return a pair of co-ordinates that could be used to draw a diagonal line from any given point for any given length.
struct MyGenerator:GeneratorType {
    var x,y,length:Int
    typealias Element = (Int,Int)
    mutating func next() -> Element? {
        x++
        y++
        length--
        if length > 0 {
            return (x,y)
        }
        else {
            return nil
        }
    }
}
First of all we've written the generator code and now we need our sequence:
struct MySequence:SequenceType {
    var x,y,length:Int
    typealias GeneratorType = MyGenerator
    func generate() -> GeneratorType {
        var gen = MyGenerator(x:x,y:y,length:length)
        return gen
    }
}
Finally we'll create an instance of the struct that adopts the Sequence protocol and then use for-in to iterate over it.
let seq = MySequence(x:10,y:10,length:10)
        
for point in seq {
    println(point)
}
Each time the for-in loop runs the generator's next() method will be called and we'll see all the co-ordinate tuples print out in the console.

Update: Bypassing the sequence

Following the introduction of the GenerateSequence type you can bypass creating a sequence for the straightforward purpose of a for-in loop. As shown in this example:
struct MyGenerator:GeneratorType {
    var x,y,length:Int
    typealias Element = (Int,Int)
    mutating func next() -> Element? {
        x++
        y++
        length--
        if length > 0 {
            return (x,y)
        }
        else {
            return nil
        }
    }
}

var a =  MyGenerator(x:10,y:10,length:10)
for x in GeneratorSequence(a) {
    x
}

Further update: Combining Generator and Sequence into one (23 November 2015)

If you don't like the GeneratorSequence(a) part to that code, then make the generator also adopt SequenceType. There's no need for any additional code to do this: 
struct MyGenerator:GeneratorType, SequenceType {
    var x,y,length:Int
    typealias Element = (Int,Int)
    mutating func next() -> Element? {
        x++
        y++
        length--
        if length > 0 {
            return (x,y)
        }
        else {
            return nil
        }
    }
}

var a =  MyGenerator(x:10,y:10,length:10)

for x in a {
    x
}
Now you get all the other stuff like map and flatMap to play with.

Further reading

Collection and Sequence Helpers (Airspeed Velocity)

Endorse on Coderwall

Comments