> [Just to complicate things... I wonder if finiteness is really
> meaningful.  It's easy to create a finite sequence that's so long that
> it's “effectively infinite.”]

And similarly, operations like `first(while:)` on an infinite sequence might or 
might not produce an infinite sequence, depending on properties of the sequence 
and predicate which aren't expressed in the type system.

I think there are really three cases:

* Known finite
* Known infinite
* Unknown

The most obvious way to model this is as two subtypes:

                Possibly infinite
                /                       \
        Infinite                Not infinite

A known-infinite iterator might refine unknown-infinite iterators like this:

        protocol InfiniteIteratorProtocol: IteratorProtocol {
                mutating func next() -> Element // Note that the return is 
non-Optional!
        }
        
        extension InfiniteIteratorProtocol {
                mutating func next() -> Self.Element? { return .some(next()) }
        } 

Refining unknown-infinite collections to provide known-infinite collections is 
trickier. I think we would want something like this:

        // We don't use Optional because its Comparable conformance puts 
`.none` 
        // before, not after, `.some`.
        enum InfiniteCollectionIndex<ConcreteIndex: Comparable>: Comparable {
                case .element (ConcreteIndex)
                case .end
                
                var concreteIndex: ConcreteIndex {
                        get {
                                switch self {
                                case .element(let i):
                                        return i
                                case .end:
                                        preconditionFailure("Attempted to 
access the end of an infinite collection")
                                }
                        }
                        set {
                                self = .element(newValue)
                        }
                }
        }
        func < <ConcreteIndex: Comparable>(lhs: 
InfiniteCollectionIndex<ConcreteIndex>, rhs: 
InfiniteCollectionIndex<ConcreteIndex>) -> Bool {
                switch (lhs, rhs) {
                case let (.element(lhsIndex), .element(rhsIndex)):
                        return lhsIndex < rhsIndex
                case (.element, .end):
                        return true
                default:
                        return false
                }
        }
        
        extension IndexingIterator: InfiniteIteratorProtocol where Elements: 
InfiniteCollection {}
                
        protocol InfiniteCollection: Collection where Index == 
InfiniteCollectionIndex<ConcreteIndex>, Iterator: InfiniteIteratorProtocol {
                typealias ConcreteIndex: Comparable
                
                var startConcreteIndex: ConcreteIndex { get }
                func index(after i: ConcreteIndex) -> ConcreteIndex
                func formIndex(after i: inout ConcreteIndex)
                
                subscript(i: ConcreteIndex) { get }
        }
        extension InfiniteCollection {
                var startIndex: InfiniteCollectionIndex<ConcreteIndex> {
                        return .element(startConcreteIndex)
                }
                var endIndex: InfiniteCollectionIndex<ConcreteIndex> {
                        return .end
                }
                func index(after i: InfiniteCollectionIndex<ConcreteIndex>) -> 
InfiniteCollectionIndex<ConcreteIndex> {
                        return .element(index(after: i.concreteIndex))
                }
                func formIndex(after i: inout 
InfiniteCollectionIndex<ConcreteIndex>) {
                        formIndex(after: &i.concreteIndex)
                }
                subscript(i: InfiniteCollectionIndex<ConcreteIndex>) {
                        return self[i.concreteIndex]
                }
        }
        // With corresponding InfiniteBidirectionalCollection and 
InfiniteRandomAccessCollection types

But the problem with this is, for all its complications, it probably doesn't 
actually provide much additional safety. "Possibly infinite" needs to have all 
the operations we ought to forbid on an infinite sequence or collection. All 
the "infinite" type can really do is provide runtime implementations that crash 
immediately; the type system can't prevent it.

The alternative is to have some kind of wrapper type you put around "possibly 
infinite" sequences/collections to essentially assert they're finite:

        // Forbidden:
        infiniteSequence.prefix(while: { $0 < 100 }).map { … }
        // Instead, write:
        infiniteSequence.prefix(while: { $0 < 100 }).assumingFinite.map { … }

But that once again brings us to the question: How important *is* it that we 
prevent greedy calls on known-infinite sequences? There aren't actually any 
known-infinite sequences in the standard library; even the `sequence` function 
can be terminated by returning `nil`. Known-infinite sequences are certainly a 
coherent concept, but are they something we need to model? And if not, is 
requiring people to say `assumingFinite` on calls where, as far as the type 
system is concerned, there *is* a possibility of a `nil` return really worth it?

(It *is* kind of a shame that we didn't keep lazy-by-default `map` and 
`filter`, because the lazy algorithms are usable on infinite sequences. But I 
understand why that wasn't possible.)

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to