Is it possible to make an Iterator that yelds?

You could hack together something like this to "simulate synchronous coroutines / generators" (though it requires Dispatch for the semaphore, and the finished variable should be made an atomic variable). Also there is no way to cancel the iterator.

import Dispatch

class Generator<T>: @unchecked Sendable {
    let anyGenerator: AnyGenerator<T>

    init(_ anyGenerator: AnyGenerator<T>) {
        self.anyGenerator = anyGenerator
    }

    func yield(_ value: T) async {
        anyGenerator.nextValue = value
        await withUnsafeContinuation { (continuation: UnsafeContinuation<Void, Never>) in
            anyGenerator.continuation = continuation
            anyGenerator.semaphore.signal()
        }
    }
}

@globalActor
actor AnyGeneratorActor {
    static let shared: AnyGeneratorActor = AnyGeneratorActor()
}

final class AnyGenerator<T>: Sequence {
    let semaphore = DispatchSemaphore(value: 0)
    var continuation: UnsafeContinuation<Void, Never>?
    var nextValue: T?
    var finished = false

    public init(_ block: @AnyGeneratorActor @Sendable @escaping (Generator<T>) async -> Void) {
        Task.detached { [self] in
            let generator = Generator(self)
            await block(generator)
            nextValue = nil
            finished = true
            continuation = nil
            semaphore.signal()
        }
    }

    func makeIterator() -> some IteratorProtocol {
        return Iterator(self)
    }

    public class Iterator<T>: @unchecked Sendable, IteratorProtocol {
        private let anyGenerator: AnyGenerator<T>

        init(_ anyGenerator: AnyGenerator<T>) {
            self.anyGenerator = anyGenerator
        }

        public func next() -> T? {
            if anyGenerator.finished { 
                return nil
            }
            anyGenerator.semaphore.wait()
            let returnValue = anyGenerator.nextValue
            anyGenerator.continuation?.resume()
            return returnValue
        }
    }
}

It can be used like this in synchronous code

let generator = AnyGenerator<(Int, Int)> { generator in
    for x in 0..<10 {
        for y in 0..<10 {
            await generator.yield((x,y))
        }
    }
}

for point in generator {
    print(point)
}
1 Like