Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 608
sirab333 Avatar asked Apr 02 '15 21:04

sirab333


2 Answers

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
            }
        }
    }
}
like image 132
Airspeed Velocity Avatar answered Nov 06 '22 04:11

Airspeed Velocity


The error message you're seeing:

'Type RandomNumberSequence' does not conform to protocol 'SequenceType'

Always means that your class or struct is missing something that the protocol declares as required.

In this case, we're missing the generate() -> Generator method. "But, it's right there!" you say? Well, it is, but it's not compiling.

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

The problem is, what if you initialize your struct with numberOfRandomNumbers less than or equal to 0? Your loop executes zero times and generate can't return anything.

I'm not sure exactly what logic you're trying to to do in this loop, but we can fix the compilation errors by simply a adding a return statement that will return a Generator:

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

This won't do what you're trying to accomplish. This isn't how generators are supposed to work. But it will fix the generate() -> Generator method and allow your struct to now conform to the protocol.

like image 41
nhgrif Avatar answered Nov 06 '22 05:11

nhgrif