SE-0346: Lightweight same-type requirements for primary associated types

Generally positive, with a few niggles.

There is a definite 'usability cliff' between using regular types and then moving to generics, where you start having to look in multiple places to see the relationships between type parameters and their constraints to work out what is going on. This definitely helps with that, and (as discussed in the Pitch) is an important step that will unlock other further improvements.

I think there's a nice symmetry between the use of <> in concrete types that adopt protocols and associated types that will become the primary associated types in these protocols. I do have slight concerns that it will muddy the waters a little on the meaning of this syntax (when understanding the difference between type parameters and associated types is already tricky enough), but not overly so.

The potential for removing from the public API surface of the standard (and other) libraries of types that are only present as a concrete return type (e.g. AsyncMapSequence, LazyMapCollection, etc would also be very helpful for reducing the cognitive load of using the corresponding methods.

There are a couple of things that makes me wonder how much of this simplification will be possible though:

  1. Conditional conformances. Some of the concrete return types (e.g. AsyncMapSequence, LazyMapCollection have conditional conformances. Will this be possible with opaque return types?

  2. @rethrows protocols. AsyncSequence and AsyncIteractorProtocol are both marked as @rethrows. So, using this from the proposal:

func readSyntaxHighlightedLines(_ file: String) -> some AsyncSequence<[Token]> {
  ...
}

How would the caller know whether or not they need to use try when using the returned value? (ie. whether they need use for try await tokens in readSyntaxHighlightedLines("filename") or for await tokens in readSyntaxHighlightedLines("filename")?). Looks like this was brought up in the pitch, but I didn't see a response.

This is also related to being able to simplify the API of AsyncSequence.map, etc. Currently, if the closure passed to map is throwing, you'll get a type that provides a throwing conformance to AsyncSequence back as the return value. If it isn't, you'll get a type that provides a non-throwing conformance to AsyncSequence. Will there be some way to abstract this in an opaque return type?

let lines = Just("line").values
let transformed = lines.map { line in line.count }
for await line in transformed {
  ...
}

let transformedThrowing = lines.map { line in throw SomeError() }
for try await line in transformedThrowing {
  ...
}

I participated in the pitch thread and thought about how this would apply to existing API designs in the standard library.

3 Likes