Swift Async Algorithms Proposal: Compacted


Proposed Solution

Similar to the Swift Algorithms package we propose that a new method be added to AsyncSequence to fit this need.

extension AsyncSequence {
  public func compacted<Unwrapped>() -> AsyncCompactedSequence<Self, Unwrapped>
    where Element == Unwrapped?

This is equivalent to writing .compactMap { $0 } from a behavioral standpoint but is easier to reason about and is more efficient since it does not need to execute or store a closure.

Detailed Design

The AsyncCompactedSequence type from an effects standpoint works just like AsyncCompactMapSequence. When the base asynchronous sequence throws, the iteration of AsyncCompactedSequence can throw. Likewise if the base does not throw then the iteration of AsyncCompactedSequence does not throw. This type is conditionally Sendable when the base, base element, and base iterator are `Sendable.

public struct AsyncCompactedSequence<Base: AsyncSequence, Element>: AsyncSequence
  where Base.Element == Element? {

  public struct Iterator: AsyncIteratorProtocol {
    public mutating func next() async rethrows -> Element?

  public func makeAsyncIterator() -> Iterator {

extension AsyncCompactedSequence: Sendable 
    Base: Sendable, Base.Element: Sendable, 
    Base.AsyncIterator: Sendable { }
extension AsyncCompactedSequence.Iterator: Sendable 
    Base: Sendable, Base.Element: Sendable, 
    Base.AsyncIterator: Sendable { }

Effect on API resilience

Compacted has a trivial implementation and is marked as @frozen and @inlinable. This removes the ability of this type and functions to be ABI resilient boundaries at the benefit of being highly optimizable.

Alternatives considered

None; shy of potentially eliding this since the functionality is so trivial. However the utility of this function aides in ease of use and approachability along with parity with the Swift Algorithms package.


This transformation function is a direct analog to the synchronous version defined in the Swift Algorithms package