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

Not with Lazy in its current form. You still need to surface enough information to drive the conditional conformance. In the case of LazyMapCollection, that conditional conformance is driven by a generic parameter Base, the collection the lazy wrapper wraps. When that wrapped collection is bidirectional, so is the lazy collection.

If you just returned an opaque some Collection<Element> that happened to be a LazyMapCollection<[Int]> then the information to drive the conditional conformance is not available to the caller (even if the compiler sometimes "knows" it for optimization purposes, if the function is inlinable), so no bidirectional conformance.

This is kind of a fundamental conflict. You cannot both keep secrets, and provide functionality based on those secrets. There has to be some give and take. There's a simpler example of this: if you are returning an [Int] but you actually return some Collection<Int> you're holding back some really useful information from the caller. They would probably love to have an Array, or at very least some RandomAccessCollection<Int>, but in order for you to preserve future flexibility to return something different, you're not giving information of which they could otherwise take advantage.

A middle ground could perhaps be to create a protocol that provides just enough information to drive the conditional conformance, while reserving other flexibility:

extension Collection {
  var lazy: some LazyCollection<Self>
}

// A protocol that provides lazy versions of common
// Collection operations.
protocol LazyCollection<
  Base: Collection
>: Collection where Element = Base.Element, Index = Base.Index {
  var base: Base { get } // unwrap the laziness of the collection
  func map(...) -> some LazyCollection<Self>
}

and then, with one additional language feature we don't yet have, do something like this:

// note this next part is not currently valid Swift,
// you can't retroactively conform protocols to other
// protocols, conditionally or otherwise
extension LazyCollection: BidirectionalCollection 
  where Base: BidirectionalCollection { }

This would allow you to get the conditional conformance benefits without exposing concrete implementations like LazyMapCollection , LazyFilterCollection.

Note that unlike Collection, this chooses a whole collection, Base, as the primary associated type for LazyCollection, not just the Element type. This allows for the unwrap operation to return the original eager collection, and follows the pattern we've a few times where primary associated types usually match generic arguments of concrete types implementing the protocol.

Of course, this is quite a lot of effort to go to purely to hide the type implementation details of things like LazyMapCollection. It may well not be worth it, especially as the Lazy types in the standard library are fragile so you don't get the ABI benefits, only the "hide the types" benefit. And the nesting e.g. some LazyCollection<some LazyCollection<some LazyCollection<[Int]>>> still gets exposed. But it may be more compelling in other similar cases.

6 Likes