I'll start with the core protocols, with some documentation added.
/// A generator for a possibly-infinite (multi-)set of values, each vended
/// asynchronously.
protocol AsynchronousIteratorProtocol {
/// The type of objects vended by the iterator.
associatedtype Element
/// The type of errors possibly thrown by `next()`.
associatedtype Error: Swift.Error
/// Returns a not-yet-vended element from the virtual set; or `nil` if that
/// set has been exhausted, a previous call threw an error, or vending has
/// been either canceled or otherwise terminated.
mutating func next() async throws(Error) -> Element?
/// Sends the instance's iteration state into termination, after being
/// called by the runtime whenever the surrounding context ends iteration
/// early.
mutating func cancel()
}
/// A coupler between a possibly-infinite (multi-)set of values and a `for`-`in`
/// loop that can visit all the members asynchronously.
protocol AsynchronousCollective {
/// The type of objects vended by the collective.
associatedtype Element
/// The generator that actually provides access to the elements.
associatedtype AsynchronousIterator: AsynchronousIteratorProtocol where AsynchronousIterator.Element == Element
/// Returns an iterator that can asynchronously visit every element of the
/// collective exactly to each value's multiplicity.
func makeIterator() -> AsynchronousIterator
/// Returns whether the given value is a member of the virtual set, or `nil`
/// if either that determination can't be optimized to be superior to linear
/// time or if `Element` doesn't conform to `Equatable`.
func _customContainsEquatableElement(_ element: Element) async throws(AsynchronousIterator.Error) -> Bool?
/// Copy as many elements as possible into the given buffer, returning a
/// continuation point for the longer operand.
func _copyContents(initializing pointer: UnsafeMutableBufferPointer<Element>) async throws(AsynchronousIterator.Error) -> (;AsynchronousIterator, UnsafeMutableBufferPointer<Element>.Index;)
}
Where throws(Whatever)
is a typed-throws specifier. A non-throwing method would use Never
as the error type and a general throwing method would use Swift.Error
as the error type (since it self-conforms). The "(; Whatever1, Whatever2 ;)
" syntax is for sum-type tuples, i.e. a replacement for the Either
type used in several functional languages.
There are several axes an asynchronous collective may be extended. There's finite vs. infinite, single- vs. multi-pass, publication order being important or not, indexed access to elements or not, mutable access to elements or not, and mutable rearrangement of elements or not.
/// An asynchronous collective that whose virtual multi-set of elements is
/// finite, meaning that all iterators that don't get canceled can call `next()`
/// only a finite number of times until `nil` gets returned.
protocol FiniteCollective: AsynchronousCollective {
/// A lower bound for the number of elements that will be vended if a
/// returned iterator is not canceled, calculated nondestructively.
var underestimatedCount: Int { get }
/// Confirms whether the collective has no elements, possibly destructively.
func checksAsEmpty() async throws(AsynchronousIterator.Error) -> Bool
/// Counts the number of elements in the collective, possibly destructively.
func postCount() async throws(AsynchronousIterator.Error) -> Int
/// Copy all the elements into a native array buffer, maintaining the order
/// vended.
__consuming func _copyToContiguousArray() async throws(AsynchronousIterator.Error) -> ContiguousArray<Element>
}
/// An asynchronous collective where the order that its elements are vended will
/// be part of the semantics, and said order is stable for a given state of the
/// virtual multi-set.
protocol AsynchronousSequence: AsynchronousCollective {}
/// An asynchronous collective that can support multiple passes, via iterators
/// that can mutate their iteration state without corrupting other instances or
/// the collective.
protocol ReusableCollective: AsynchronousCollective {}
/// An asynchronous collective with even stronger support for multiple passes,
/// via iterators that are safe from corruption even if the collective is later
/// mutated.
protocol ReusablePureCollective: ReusableCollective {}
/// An asynchronous collective that vends its elements in the same order from
/// every returned iterator instance (as long as the virtual multi-set isn't
/// mutated).
protocol PersistentCollective: ReusableCollective {}
/// An asynchronous collective that can also nondestructively reference its
/// elements directly via index tokens.
protocol IndexedCollective: ReusableCollective {
/// A token to directly access an element.
associatedtype Index: Equatable
/// A set of tokens for direct access to all the elements.
associatedtype Indices: IndexedCollective where Indices.Element == Index, Indices.Index == Index
/// Accesses the element that the given token points to.
subscript(position: Index) -> Element { get }
/// A collective providing all of this collective's elements' tokens.
var indices: Indices { get }
/// Returns the index of some element of the collective that equals the
/// given value, `.some(nil)` if no element matches the value, or `nil` if
/// either that determination can't be optimized to be superior to linear
/// time or if `Element` doesn't conform to `Equatable`.
func _customIndexOfEquatableElement(_ element: Element) async throws(Indices.AsynchronousIterator.Error) -> Index??
}
/// An asynchronous collective that can mutate its elements.
protocol MutableCollective: IndexedCollective {
/// Accesses the element that the given token points to, for both read and
/// write.
subscript(position: Index) -> Element { get set }
/// Exchanges the values at the specified indices of the collective.
mutating func swapAt(_ i: Index, _ j: Index)
}
/// An asynchronous collective that can remove arbitrary elements or reserve
/// space for new ones.
protocol DeletingCollective: IndexedCollective, FiniteCollective where Indices: FiniteCollective {
/// The type of errors possibly thrown by `remove(along:)`.
associatedtype RemovalError: Swift.Error
/// Prepares, as well as possible, the collective to store the given number
/// of elements.
mutating func reserveCapacity(_ amount: Int)
/// Removes the elements at the given indices.
mutating func remove<C: AsynchronousCollective>(along: C) async throws(RemovalError) where C.Element == Index
/// Remove every element from the collective.
mutating func removeAll(keepingCapacity: Bool)
}
You can generally just compose protocols for the right capabilities you need. I didn't put the methods like forEach
or map
; I'll figure that out tomorrow. There are some combinations of protocols that enable additional customization points.
/// An asynchronous collective that may cache its elements in a memory buffer.
protocol PersistentFiniteCollective: PersistentCollective, FiniteCollective {
/// Returns the result of applying the given closure to the in-memory buffer
/// caching the elements of this collective, either creating the buffer if
/// necessary or returning `nil` instead.
func withContiguousStorageIfAvailable<R>(
_ body: (UnsafeBufferPointer<Element>) throws -> R
) rethrows -> R?
}
/// An asynchronous collective that can mutate its elements through a buffer.
protocol MutablePersistentFiniteCollective: PersistentFiniteCollective {
/// Returns the result of applying the given closure to the in-memory
/// writable buffer containing the elements of this collective, either
/// creating the buffer if necessary or returning `nil` instead.
mutating func withContiguousMutableStorageIfAvailable<R>(
_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R
) rethrows -> R?
}
Including, of course, an adaptation of the Collection
hierarchy.
/// An asynchronous sequence that can also nondestructively reference its
/// elements directly via index tokens and provide linear traversal of said
/// tokens.
protocol AsynchronousCollection
: AsynchronousSequence, PersistentFiniteCollective, IndexedCollective
where Index: Comparable, Indices: AsynchronousCollection, Indices.SubSequence == Indices, Indices.AsynchronousIterator.Error == Never
{
/// The type representing contiguous subsequences (*i.e.* substrings) of the
/// collection.
associatedtype SubSequence: AsynchronousCollection where SubSequence.Element == Element, SubSequence.Index == Index, SubSequence.SubSequence == SubSequence
/// The position of the first element in a nonempty collection.
var startIndex: Index { get }
/// The collection's "past the end" position—that is, the position one
/// greater than the last valid subscript argument.
var endIndex: Index { get }
/// Accesses a contiguous subrange of the collection's elements.
subscript(bounds: Range<Index>) -> SubSequence { get }
/// Returns the index of first element of the collection that equals the
/// given value, `.some(nil)` if no element matches the value, or `nil` if
/// either that determination can't be optimized to be superior to linear
/// time or if `Element` doesn't conform to `Equatable`.
func _customFirstIndexOfEquatableElement(_ element: Element) async -> Index??
/// Returns the index of last element of the collection that equals the
/// given value, `.some(nil)` if no element matches the value, or `nil` if
/// either that determination can't be optimized to be superior to linear
/// time or if `Element` doesn't conform to `Equatable`.
func _customLastIndexOfEquatableElement(_ element: Element) async -> Index??
/// Returns an index that is the specified distance from the given index.
func index(_ i: Index, offsetBy distance: Int) -> Index
/// Returns an index that is the specified distance from the given index,
/// unless that distance is beyond a given limiting index.
func index(
_ i: Index, offsetBy distance: Int, limitedBy limit: Index
) -> Index?
/// Returns the distance between two indices.
func distance(from start: Index, to end: Index) -> Int
/// Returns the position immediately after the given index.
func index(after i: Index) -> Index
/// Replaces the given index with its successor.
func formIndex(after i: inout Index)
}
/// An asynchronous sequence that can also nondestructively reference its
/// elements directly via index tokens and provide linear traversal of said
/// tokens in both the forward and backward directions.
protocol AsynchronousBidirectionalCollection: AsynchronousCollection {
/// Returns the position immediately before the given index.
func index(before i: Index) -> Index
/// Replaces the given index with its predecessor.
func formIndex(before i: inout Index)
}
/// An asynchronous sequence that can also nondestructively reference its
/// elements directly via index tokens and provide efficient random-access
/// traversal of said tokens.
protocol AsynchronousRandomAccessCollection: AsynchronousBidirectionalCollection {}
/// An asynchronous sequence that can also nondestructively reference its
/// elements for reading and writing directly via index tokens.
protocol AsynchronousMutableCollection: AsynchronousCollection, MutableCollective, MutablePersistentFiniteCollective {
/// Accesses a contiguous subrange of the collection's elements for reading
/// and writing.
subscript(bounds: Range<Index>) -> SubSequence { get set }
/// Reorders the elements of the collection such that all the elements that
/// match the given predicate are after all the elements that don't match.
mutating func partition(by belongsInSecondPartition: (Element) throws -> Bool) rethrows -> Index
/// Moves the subsequence of elements before the given index to be after the
/// subsequence of elements starting at the given index, preserving the
/// relative order of elements within each partition.
mutating func swapPartitions(across pivot: Index) -> Index
}
/// An asynchronous sequence that can add, remove, or replace elements while
/// nondestructively referencing those elements via index tokens.
protocol AsynchronousRangeReplaceableCollection: AsynchronousCollection, DeletingCollective {
/// Creates a new, empty collection.
init()
/// Replaces the specified subrange of elements with the given collection.
mutating func replaceSubrange<C: AsynchronousCollection>(
_ subrange: Range<Index>,
with newElements: __owned C
) async throws(C.AsynchronousIterator.Error) where C.Element == Element
/// Creates a new collection containing the specified number of a single,
/// repeated value.
init(repeating repeatedValue: Element, count: Int)
/// Creates a new instance of a collection containing the elements of a
/// sequence.
init<S: AsynchronousSequence>(_ elements: S)
where S.Element == Element
/// Adds an element to the end of the collection.
mutating func append(_ newElement: __owned Element)
/// Adds the elements of a sequence or collection to the end of this
/// collection.
mutating func append<S: AsynchronousSequence>(contentsOf newElements: __owned S)
where S.Element == Element
/// Inserts a new element into the collection at the specified position.
mutating func insert(_ newElement: __owned Element, at i: Index)
/// Inserts the elements of a sequence into the collection at the specified
/// position.
mutating func insert<S: AsynchronousCollection>(contentsOf newElements: __owned S, at i: Index)
where S.Element == Element
/// Removes and returns the element at the specified position.
@discardableResult
mutating func remove(at i: Index) -> Element
/// Removes the specified subrange of elements from the collection.
mutating func removeSubrange(_ bounds: Range<Index>)
/// Removes and returns the first element of the collection.
@discardableResult
mutating func removeFirst() -> Element
/// Removes the specified number of elements from the beginning of the
/// collection.
mutating func removeFirst(_ k: Int)
/// Removes all the elements that satisfy the given predicate.
mutating func removeAll(
where shouldBeRemoved: (Element) throws -> Bool) rethrows
}