Proposal: Allow for/in to take GeneratorType in addition to SequenceType


(Michael Henson) #1

Currently, the construct:

for x in something {}

expects "something" to be a SequenceType. From that variable, the
underlying code retrieves a generator by calling something.generate(). The
resulting GeneratorType instance is unavailable to calling code.

Unless there is a reason for the language to require a SequenceType, it
seems like there are good use cases for accepting a caller-provided
GeneratorType, too.

For example, it would allow continuable iterations, or more generally, the
results of the loop contruct to maintain state.

struct ExampleGenerator: GeneratorType {
   typealias Element = Int

   var current: Int
   private var initial: Int

   mutating func next() -> Element? {
      guard self.current <= self.initial + 10 else {
        return nil
      }

      self.current += 1

      return self.current
    }
}

struct ExampleSequence: SequenceType {
    let start: Int

    func generate() -> ExampleGenerator {
      return ExampleGenerator(start: self.start)
    }
}

With the current mechanism:

var seq = ExampleSequence(start: 5)
for x in seq {
  if (x > 7) {
    break
  }
  print(x)
}

result:
5
6
7

for y in seq {
  print(y)
}

result:
5
6
7
8
9
10
... etc

If we could pass a Generator:

var gen = ExampleGenerator(start: 5)

for x in gen {
  if(x > 7) {
    break
  }
  print(x)
}

result:
5
6
7

for y in gen {
  print(y)
}

result:
8
9
10
... etc

Given that the provided generator also is a persistent data structure, it
could easily be used as the sink for the results of one or multiple for/in
loops, such as to aggregate statistics about the iterated items, count the
number of iterations, etc.

Are there downsides in the implementation or design that make this a bad
idea?

Mike


(Dmitri Gribenko) #2

I support this. We would need to define what happens when the expression
is both a generator and a sequence, but otherwise I don't see why not.

Dmitri

···

On Sun, Dec 13, 2015 at 3:43 PM, Michael Henson via swift-evolution < swift-evolution@swift.org> wrote:

Currently, the construct:

for x in something {}

expects "something" to be a SequenceType. From that variable, the
underlying code retrieves a generator by calling something.generate(). The
resulting GeneratorType instance is unavailable to calling code.

Unless there is a reason for the language to require a SequenceType, it
seems like there are good use cases for accepting a caller-provided
GeneratorType, too.

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Dave Abrahams) #3

When the language is able to handle recursive protocol requirements, this will become redundant, because GeneratorType will refine SequenceType (with generate returning self). So, IMO, this isn't a problem worth solving separately.

-Dave

···

On Dec 13, 2015, at 4:24 PM, Dmitri Gribenko via swift-evolution <swift-evolution@swift.org> wrote:

On Sun, Dec 13, 2015 at 3:43 PM, Michael Henson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Currently, the construct:

for x in something {}

expects "something" to be a SequenceType. From that variable, the underlying code retrieves a generator by calling something.generate(). The resulting GeneratorType instance is unavailable to calling code.

Unless there is a reason for the language to require a SequenceType, it seems like there are good use cases for accepting a caller-provided GeneratorType, too.

I support this. We would need to define what happens when the expression is both a generator and a sequence, but otherwise I don't see why not.