Pitch: throwing sequences aka Cursor


#1

Hello,

I'd like to discuss the opportunity to extend the Swift Standard Library with a new protocol and a family of concrete types.

The problem: Sequence and IteratorProtocol do not throw. They are unsuitable for external resources like database rows, file contents, socket bytes, etc. That's a pity, because the Standard Library provides a full suite of methods like map, flatMap, filter, first, reduce, etc that are both useful, and unavailable for I/O sequences.

This email provides the first draft for a new protocol, Cursor, and a family of reusable methods and concrete types that help handling untrustable sequences.

let cursor = /* Cursor of Int */
while let int = try cursor.next() {
    print(int)
}

Ideally, the Swift compiler would provide sugar for Cursor, and let the user write:

// Handle errors in both cursor creation and iteration, in a single `for` statement
for try int in try makeCursor() {
    print(int)
}

Cursor share traits from both LazySequenceProtocol and IteratorProtocol, but differs in several ways:

- Cursor types are classes, and have a lifetime. This is because Cursor aims at wrapping external resources, and has a non-mutating iteration method (because it is the external resource that mutates on iteration).
- Cursor iteration may throw errors.
- A cursor can not be repeated.

public protocol Cursor : class {
    /// The type of element traversed by the cursor.
    associatedtype Element
    
    /// Advances to the next element and returns it, or nil if no next element
    /// exists. Once nil has been returned, all subsequent calls return nil.
    func next() throws -> Element?
}

Cursor can implement the classic contains(_:), contains(where:), enumerated(), first(where:), flatMap(ElementOfResult), flatMap(SequenceOfResult), flatMap(CursorOfResult), forEach(_:), joined(), map(_:), etc.

Like lazy sequence, Cursor.filter, map, flatMap, etc return other cursors.

One can derive an Array from a cursor, with try Array(cursor).

This is enough for a pitch, and grab your comments, if you find that Cursor would be a nice addition to the Standard Library.

There is already a working implementation of Cursor, that fuels the manipulation of database results in the next version of the GRDB.swift <https://github.com/groue/GRDB.swift/pull/148> library: see https://github.com/groue/GRDB.swift/blob/fc1fdab51a91dedc7f5233b32440f8e8411cdf0a/GRDB/Core/Cursor.swift

Gwendal Roué