6

Can I create a generator in Swift?

With iterator, I need store intermediate results, for example:

struct Countdown: IteratorProtocol, Sequence {

    private var value = 0

    init(start: Int) {
        self.value = start
    }

    mutating func next() -> Int? {
        let nextNumber = value - 1
        if nextNumber < 0 {
            return nil
        }

        value -= 1

        return nextNumber
    }
}

for i in Countdown(start: 3) {
    print(i)
} // print 1 2 3

In this example, I need store the value.

In my situation, I want to use generator instead of iterator, because I don't want store the intermediate results of my sequence in each next.

macabeus
  • 4,156
  • 5
  • 37
  • 66
  • Could you add a bit more description? (I didn't down-vote you.) Not understanding Python - but knowing it's just another language - I googled what a Python "generator' is. So now that I understand it's a simple iteration loop... well, (1) what exactly is "yield"? and (2) What - in general, agnostic language terms - are you trying to do? –  May 30 '17 at 03:54
  • @dfd I use code in Python because I didn't know how to express what is "yield" and what is "generator". Well... with "yield", you return to expression that called the generator and, in end of the loop, return to last yield called. It's very useful if you don't want store the intermediate results in each iterator - exactly my case. Anyway, I edited my question. – macabeus May 30 '17 at 04:18
  • 1
    Swift has no "yield" statement or coroutines, compare https://stackoverflow.com/questions/43505101/swift-equivalent-of-unity3d-coroutines. – Martin R May 30 '17 at 05:20
  • Here is an emulation using threads: https://github.com/JadenGeller/Yield (I did not try it). – Martin R May 30 '17 at 05:26
  • @MartinR this is already built into swift. See my answer below. – WaterNotWords Dec 03 '17 at 03:50
  • "I don't want store the intermediate results of my sequence in each next." To what end? It is very rare to create an Iterator by hand in Swift (your above code is much more naturally written `for i in (0..<3).reversed() { print(i) }`). Are you trying to save a single line of code in a rare construct? It feels like you're fighting the language; what problem are you solving? (As others have noted, some day Swift will likely have coroutines, but it doesn't today, and you shouldn't try to force it to.) – Rob Napier Dec 03 '17 at 04:48
  • 1
    I'm currently working on a proposal to introduce syntax support for generators in Swift and looking for feedback on this. I described it in a bit more details here https://gist.github.com/maxdesiatov/8ae5c0eb747cbda47e641a8e423a1e83 – Max Desiatov Sep 10 '18 at 09:35
  • Your (edited out) python generator and Swift iterator both save the intermediate value (until the next loop iteration overwrites it). You *can* tweak python so that the intermediate value is gc'ed before the control returns to the generator (assuming you don't need it for next iteration!). See https://stackoverflow.com/questions/7133179/python-yield-and-delete and note that it's implementation-dependent. You cannot do the same in Swift, but see https://github.com/apple/swift/blob/master/docs/OwnershipManifesto.md for future plans that might allow it. – max Dec 25 '19 at 11:40

4 Answers4

9

Understanding how generators work (and why they are less important in swift) is at first difficult coming from Python.

Up to Swift v2.1 there was a protocol called GeneratorType. This was renamed to IteratorProtocol in Swift v3.0+. You can conform to this protocol to make your own objects that do just-in-time computations similar to what can be done in Python.

More information can be found in the Apple Documentation: IteratorProtocol

A simple example from IteratorProtocol page:

struct CountdownIterator: IteratorProtocol {
    let countdown: Countdown
    var times = 0

    init(_ countdown: Countdown) {
        self.countdown = countdown
    }

    mutating func next() -> Int? {
        let nextNumber = countdown.start - times
        guard nextNumber > 0
            else { return nil }

        times += 1
        return nextNumber
    }
}

let threeTwoOne = Countdown(start: 3)
for count in threeTwoOne {
    print("\(count)...")
}
// Prints "3..."
// Prints "2..."
// Prints "1..."

However, you need to think about why you are using a generator:

Swift automatically does something "called copy on write." This means that many of the cases that use a Python generator to avoid the large copying cost of collections of objects (arrays, lists, dictionaries, etc) are unnecessary in Swift. You get this for free by using one of the types that use copy on write.

Which value types in Swift supports copy-on-write?

It is also possible to use a wrapper to force almost any object to be copy on write, even if it is not part of a collection:

How can I make a container with copy-on-write semantics?

The optimizations in swift usually mean that you do not not have to write generators. If you really do need to (usually because of data heavy, scientific calculations) it is possible as above.

WaterNotWords
  • 997
  • 1
  • 9
  • 24
  • The OP was asking how to avoid storing an intermediate value; neither copy-on-write, nor your iterator protocol example, avoids storing the intermediate value. (For that matter, the OP's original python code also stored the intermediate value, but see my comment to the OP's question.) – max Dec 25 '19 at 11:48
1

Based on the code you provided and the little bit knowledge of generators that I do have, you can do something like

struct Countdown {
    private var _start = 0
    private var _value = 0

    init(value: Int) {
        _value = value
    }

    mutating func getNext() -> Int? {
        let current = _start
        _start += 1
        if current <= _value {
            return current
        } else {
            return nil
        }
    }
}

and then wherever you want to use it, you can do something like

var counter = Countdown(value: 5)
while let value = counter.getNext() {
    print(value)
}
Malik
  • 3,763
  • 1
  • 22
  • 35
1

I have a solution similar to above, but with a slightly more "yield-y" feeling to it.

struct Countdown
{
    static func generator(withStart: Int) -> () -> Int?
    {
        var start = withStart + 1
        return {
            start = start - 1
            return start > 0 ? start : nil
        }
    }
}

let countdown = Countdown.generator(withStart: 5)

while let i = countdown()
{
    print ("\(i)")
}
XmasRights
  • 1,427
  • 13
  • 21
1

Walter provides a lot of good information, and generally you shouldn't be doing this in Swift, but even if you wanted an Iterator, the right way to do it is with composition, not by building your own. Swift has a lot of existing sequences that can be composed to create what you want without maintaining your own state. So in your example, you'd differ to a range's iterator:

struct Countdown: Sequence {

    private var value = 0

    init(start: Int) {
        self.value = start
    }

    func makeIterator() -> AnyIterator<Int> {
        return AnyIterator((0..<value).reversed().makeIterator())
    }
}

for i in Countdown(start: 3) {
    print(i)
} // print 1 2 3

Something has to keep the state; that's the nature of these kinds of functions (even in a world with coroutines). It's fine not to maintain it directly; just delegate to a more primitive type. Swift has a couple of dozen built-in Iterators you can use to build most things you likely need, and any iterator can be lifted to an AnyIterator to hide the implementation details. If you have something custom enough that it really requires a next(), then yes, storing the state is your problem. Something has to do it. But I've found this all to be extremely rare, and often suggests over-design when it comes up.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610