SE-0231 — Optional iteration

Anyone who wants to be able to do this:

for e in someOptionalSequence.? {
    print(e)
}

can already do so (for any sequence, including CollectionOfOne), using eg this:

struct FlattenedOptionalSequence<Base: Sequence>: IteratorProtocol, Sequence {
    let baseNext: () -> Base.Element?
    init(_ optionalSequence: Optional<Base>) {
        switch optionalSequence {
        case .none: baseNext = { return nil }
        case .some(let s):
            var baseIterator = s.makeIterator()
            baseNext = { return baseIterator.next() }
        }
    }
    func makeIterator() -> FlattenedOptionalSequence<Base> { return self }
    mutating func next() -> Base.Element? { return baseNext() }
}
extension Optional where Wrapped: Sequence {
    func flattenedOptionalSequence() -> FlattenedOptionalSequence<Wrapped> {
        return FlattenedOptionalSequence(self)
    }
}

postfix operator .?

postfix func .?<S: Sequence>(lhs: Optional<S>) -> FlattenedOptionalSequence<S> {
    return FlattenedOptionalSequence(lhs)
}

It will also work for @Ben_Cohen's above example:

let maybeHugeRange: Range<Int>? = 0..<Int.max

for i in (maybeHugeRange?.reversed()).? {
    print(i) // Will print 9223372036854775806 and break.
    break
}

Although unfortunately, because of the way optional chaining works, we have to add those parens around everything before the postfix .? operator, since that's the only way to refer to the optional created by optional chaining rather than the non-optional "in-chain value". We could change .? into a prefix operator and get rid of the parens that way:

for i in .?maybeHugeRange?.reversed() { ... }

I don't know if the baseNext closure would impact performance negatively or if there would be a way to work around that.


EDIT: There's also the possibility to

make any optional sequence a sequence
public struct FlattenedOptionalSequence<Base: Sequence>: IteratorProtocol, Sequence {
    let baseNext: () -> Base.Element?
    init(_ optionalSequence: Optional<Base>) {
        switch optionalSequence {
        case .none: baseNext = { return nil }
        case .some(let s):
            var baseIterator = s.makeIterator()
            baseNext = { return baseIterator.next() }
        }
    }
    public func makeIterator() -> FlattenedOptionalSequence<Base> { return self }
    public mutating func next() -> Base.Element? { return baseNext() }
}

extension Optional: Sequence where Wrapped: Sequence {
    public typealias Element = Wrapped.Element
    public typealias Iterator = FlattenedOptionalSequence<Wrapped>

    public func makeIterator() -> FlattenedOptionalSequence<Wrapped> {
        return FlattenedOptionalSequence(self)
    }
}

so that for-in will "just work" with any number of optional layers around any sequence:

let oooa: [Int]??? = [1, 2, 3]
for e in oooa { print(e) } // Prints 1 2 3

let maybeHugeRange: Range<Int>? = 0..<Int.max
for i in maybeHugeRange?.reversed() {
    print(i) // Will print 9223372036854775806 and break.
    break
}
2 Likes