[Pitch] Generalize `AsyncSequence` and `AsyncIteratorProtocol`

This pitch was reviewed, accepted, and implemented for Swift 6.0: swift-evolution/proposals/0421-generalize-async-sequence.md at main · apple/swift-evolution · GitHub

Ah Amazing, sorry I was looking at the wrong link and thought it still needed to go through review :smile:

Primary associated types on AsyncSequence would enable hiding concrete implementation details behind constrained opaque or existential types, such as in transformation APIs on AsyncSequence.

Now that this is implemented, what’s stopping the adoption of opaque return types in the AsyncSequence operators of the standard library?

As I see it, this would be the last piece of the puzzle to enable the use of existential AsyncSequence types in interfaces and eliminate the type erasure confusion raised by many here and here.

The current signature of AsyncSequence operators uses concrete types, which most of the time have a generic Base requirement:

func map<Transformed>(
  _ transform: @escaping @Sendable (Self.Element) async -> Transformed
) -> AsyncMapSequence<Self, Transformed>

Using Self as a generic parameter in the operator signature prevents using it on existential AsyncSequences:

protocol ExistentialService {
    func get() -> any AsyncSequence<String, Error>
}

protocol GenericService {
    associatedtype GetResult: AsyncSequence<String, Error>
    func get() -> GetResult
}

func consume(service: ExistentialService) {
    service.get().map { // Member 'map' cannot be used on value of type 'any AsyncSequence<String, any Error>'; consider using a generic constraint instead
        $0.capitalized
    }
}

func consume(service: any GenericService) {
    service.get().map { // Member 'map' cannot be used on value of type 'any AsyncSequence<String, any Error>'; consider using a generic constraint instead
        $0.capitalized
    }
}

Given map would hide its implementation details with an opaque return type, the example above would compile just fine:

extension AsyncSequence {
    func map<Transformed>(
        _ transform: @escaping @Sendable (Self.Element) async -> Transformed
    ) -> some AsyncSequence<Transformed, Failure> {
        AsyncMapSequence<Self, Transformed>(base: self, transform: transform)
    }
}
3 Likes