Tim Vermeulen Tim Vermeulen - 1 year ago 38
Swift Question

Strange SequenceType behaviour when using a wrapper

I have a wrapper that wraps a sequence:

struct SequenceWrapper<T>: SequenceType {
var sequence: AnySequence<T>

func generate() -> AnyGenerator<T> {
return sequence.generate()

let wrapper = SequenceWrapper(sequence: AnySequence(1 ... 10))

If I make two generators and call
on both, nothing strange happens:

let generator = wrapper.generate()
let another = wrapper.generate()

generator.next() // 1
another.next() // 1

So far, so good. However, if I first call
on my wrapper, I get strange behaviour:

let wrapper = SequenceWrapper(sequence: AnySequence(1 ... 10))
let trimmed = wrapper.dropFirst()

let generator = trimmed.generate()
let another = trimmed.generate()

generator.next() // 2
another.next() // 3, huh?

If I use
instead of
, the outputs are
, as expected.

If I don't use the wrapper
I made, but a direct
instance instead, nothing out of the ordinary happens:

let sequence = AnySequence(1 ... 10)
let trimmed = sequence.dropFirst()

let generator = trimmed.generate()
let another = trimmed.generate()

generator.next() // 2
another.next() // 2, as expected

I can't make any sense of this. What's going on here?


You would have to debug through the behavior to understand the specific behavior, but this most likely happens because SequenceType is not required to be iterable multiple times.

SequenceType makes no requirement on conforming types regarding whether they will be destructively "consumed" by iteration. To ensure non-destructive iteration, constrain your sequence to CollectionType.

As a consequence, it is not possible to run multiple for loops on a sequence to "resume" iteration:

for element in sequence {
  if ... some condition { break }

for element in sequence {
  // Not guaranteed to continue from the next element.
  // [mine: not guaranteed to restart from the beginning either]

SequenceType makes no requirement about the behavior in that case. It is not correct to assume that a sequence will either be "consumable" and will resume iteration, or that a sequence is a collection and will restart iteration from the first element. A conforming sequence that is not a collection is allowed to produce an arbitrary sequence of elements from the second generator.

In other words, the two generators that you get from calling generate() twice are not guaranteed to not interfere with one another. In fact, it's legal for a type to implement both SequenceType and GeneratorType and implement generate() as return self. Any shared state (from member references, or if the sequence itself is a reference type) will be shared across generators, which is why you should only make one.