Here’s my situation. I have an endless sequence that generates integers from min to max and back. So I initially figured, “let me just make a generator”. (I’m not saying this is a good generator. I just want to know if I should be using a generator here.)
public struct UpAndDownIntGenerator: GeneratorType { public typealias Element = Int let (magnitude, period): (Int, Int) let minValue: Int var currentOffset: Int = 0 public init(minValue: Int = 0, maxValue: Int) { assert(minValue < maxValue, "minValue must be less than maxValue") self.minValue = minValue magnitude = maxValue - minValue period = magnitude * 2 } public mutating func next() -> Int? { let value = currentOffset % period let adjustedValue = value % magnitude let isAscending = value < magnitude defer { currentOffset += 1 } return minValue + (isAscending ? adjustedValue : magnitude - adjustedValue) } }
But this generator is never going to terminate and I know it will always return a non-nil value. So I added this:
// This is not going to be fed into a sequence // so fetch the next value and test for nil public mutating func fetchNextValue() -> Element { guard let value = next() else { fatalError("unable to generate next value") } return value }
And then I was all: So why bother being a generator with the extra overhead? Why am I creating something that has the hallmarks of a generator but isn’t suitable for use in a sequence (for example, mapping over an infinite sequence or filtering, or just figuring how to grab one value at a time). So then I wrote this:
public struct UpAndDownProducer { let (magnitude, period): (Int, Int) let minValue: Int var currentOffset: Int = 0 public init(minValue: Int = 0, maxValue: Int) { assert(minValue < maxValue, "minValue must be less than maxValue") self.minValue = minValue magnitude = maxValue - minValue period = magnitude * 2 } public mutating func next() -> Int { let value = currentOffset % period let adjustedValue = value % magnitude let isAscending = value < magnitude defer { currentOffset += 1 } return minValue + (isAscending ? adjustedValue : magnitude - adjustedValue) } }
So what should I actually be doing here? Advice greatly appreciated.
p.s. The following would be a much simpler approach:
public struct UpAndDownProducer { let minValue, maxValue: Int var currentValue: Int var direction = -1 public init(minValue: Int = 0, maxValue: Int) { assert(minValue != maxValue, "No point going up and down between two equal values") // Since it starts at minValue, it's // going to flip immediately. if maxValue < minValue { direction = 1 } currentValue = minValue (self.minValue, self.maxValue) = (minValue, maxValue) } public mutating func next() -> Int { defer { if currentValue == minValue || currentValue == maxValue { direction *= -1 } currentValue += direction } return currentValue } }
p.p.s. Davide De Franceschi suggests something along the following lines:
protocol EndlessGeneratorType: GeneratorType {} extension EndlessGeneratorType { public mutating func someNext() -> Element { guard let element = next() else { fatalError("EndlessGeneratorType must always have a next() element") } return element } }
he adds:
@jckarter @ericasadun @tTikitu @vvendra imho it's always best to conform to relevant protocols: present + future + 3rd party free goodies
— Davide De Franceschi (@DeFrenZ) March 17, 2016
6 Comments
I guess it depends more than anything on what you want to use it for.
If it’s for some kind of for-in loop (with a break, I assume, so that it doesn’t loop forever) the generator seems to be better because it supports this syntax. If there’s a possibility of using some lazy implementation of map or filter, that would be better too.
Otherwise (i.e. if we’re just throwing the function call into a variable), the second alternative may make it easier since the return value isn’t an optional.
I can’t provide guidance on the Generator issue, but I am curious about “let (magnitude, period): (Int, Int)” versus “let magnitude, period: Int”. The former tuple pattern makes it marginally easier to replace one of the variable data types, but not the other. But if they weren’t all the same data type then it’s arguable that they shouldn’t be declared on the same line. Visually scanning to find the nth variable name and matching it to the nth type in a comma separated list is a bit much. When they are all the same data type is there a benefit to using the tuple pattern?
I was messing around with bunches of different implementation types, and it started out with one being int and another float and then became int.
Honestly, a much better approach is max, min, increasing, which flips each time you hit min or max
For the reasons you describe, I like your 2nd implementation better. The GeneratorType implementation should only be used if you actually need to supply it to an interface that requires a GeneratorType.
I third what Vinicius and Michael are saying. Speaking more generally, I’d suggest that the decision comes down to “what would communicate my design intent most clearly?”
An analogous example might be a RNG. While a RNG is also like a Swift Generator in that it iteratively produces output when you call “next”, I doubt that a native Swift RNG would ever be implemented as a Generator. There’s too big a semantic distinction between iterating over a Sequence versus iteratively producing output. The abstraction of a Generator communicates design intent as much as it serves a common API, and the design intent is too different.
I’m not sure if there would be a suitable abstraction for an iterative function like your up-and-down or a RNG, other than just a closure of type () -> (Int). “Producer” I think communicates that well. Or maybe “Number Fountain”. 😉
I’m actually surprised that you can’t conform to GeneratorType with a next() that returns a non-optional Element; if GeneratorType were a generic class, an override could remove the optionality from the return type.
I think I would probably make an InfiniteGeneratorType protocol like this: https://gist.github.com/brentdax/8898f61152b7431b6aa4