Search code examples
swiftforeachgeneratorsequencefor-in-loop

Swift SequenceType not working


I'm trying to implement a SequenceType / GeneratorType example and getting an error that doesn't quite make sense.

Here's the code:

// Here's my GeneratorType - it creates a random-number Generator:
struct RandomNumberGenerator:GeneratorType {
    typealias Element = Int
    mutating func next() -> Element? {
       return Int(arc4random_uniform(100))
    }
} 

When I call this (in Playgrounds) it works perfectly well:

var randyNum = RandomNumberGenerator()
randyNum.next()   // this shows a valid random number in the Gutter
// And calling it from within a println also works:
println("randyNum = \(randyNum.next()!)")

So far so so good.

Next is the SequenceType:

struct RandomNumbersSequence:SequenceType {
    typealias Generator = RandomNumberGenerator
    var numberOfRandomNumbers:Int

    init(maxNum:Int) {
        numberOfRandomNumbers = maxNum
    }

    func generate() -> Generator {
        for i in 1...numberOfRandomNumbers {
          var randNum = Generator()
          randNum.next()
          return randNum
       }
    } 

}

That's what's generating an error: 'Type RandomNumberSequence' does not conform to protocol 'SequenceType'. (Xcode is showing this error right on that first line of the declaration struct RandomNumbersSequence:SequenceType statement.)

I actually think the logic of my for loop may be wrong - meaning I won't get the results I actually want - but regardless, in terms of satisfying what the SequenceType Protocol is requiring, I think I got that much right. So what's causing this error?


Solution

  • This isn’t quite how generators work. Assuming you want to serve up a set number of random numbers, you have the right idea about taking a maximum as a parameter, but you need to store that in the generator, as well, plus some state for where it is up to.

    The idea with a generator is that it stores its state, and every time you call next() you return the next element. So if you want to generate a run of up to n numbers, you could do something like the following:

    struct RandomNumberGenerator: GeneratorType {
        let n: Int
        var i = 0
        init(count: Int) { self.n = count }
    
        mutating func next() -> Int? {
            if i++ < n {
                return Int(arc4random_uniform(100))
            }
            else {
                return nil
            }
        }
    }
    

    Note, you don’t need a for loop here. Just each time next() is called, i is incremented, until it reaches the max, then the generator starts returning nil.

    The purpose of SequenceType is to serve up fresh generators:

    struct RandomNumberSequence: SequenceType {
        let n: Int
        init(count: Int) { self.n = count }
    
        func generate() -> RandomNumberGenerator {
            return RandomNumberGenerator(count: n)
        }
    }
    

    Given this, you can now use it to generate a sequence of a fixed number of random integers:

    let seq = RandomNumberSequence(count: 3)
    
    for x in seq {
         // loops 3 times with x being a new random number each time
    }
    
    // every time you use seq, you get a new set of 3 numbers
    ",".join(map(seq,toString))  // prints 3 comma-separated random nums
    
    // but when you create a generator, it gets “used up”
    var gen = seq.generate()
    println(gen.next()) // prints a random number
    println(gen.next()) // prints another random number
    println(gen.next()) // prints the third
    println(gen.next()) // prints nil
    println(gen.next()) // and will keep printing nil
    
    gen = seq.generate()
    println(gen.next()) // will print the first of a new set of 3 numbers
    

    Creating these stateful generators is quite a common problem, so the standard library has a helper struct, GeneratorOf, that allows to you skip defining them. It takes a closure that each time it is called should return the next value to generate:

    struct RandomNumbersSequence: SequenceType {
        let maxNum: Int
    
        init(maxNum: Int) { self.maxNum = maxNum }
    
        func generate() -> GeneratorOf<Int> {
            // counter to track how many have been generated
            var n = 0
            return GeneratorOf {
                // the closure “captures” n
                if n++ < self.maxNum {
                    return Int(arc4random_uniform(100))
                }
                else {
                    return nil
                }
            }
        }
    }