Rename protocols that use Self or associated types to “constraints”, and declare them as such

Even methods that use associated types could be made available, either by explicitly opening the dynamic type of the existential, or using path-dependent types to represent the dynamic type implicitly:

let x: Collection = ...
let start /*: x.Index*/ = x.startIndex
// We know we can pass start back into x.index(after:), because its Index type
// came from x's dynamic type
let second = x.index(after: start)
let firstValue /*: x.Element*/ = x.first!
let hasDuplicate = x[second ..< x.endIndex].contains(firstValue)

This would let you use most operations on a single dynamic protocol value in the way you'd expect, even if it has associated types. It's true you would need common constraints between two different values to take computations based on one and use them with the other, and we'd need a good way to describe how that works that's probably more detailed than how we describe protocols-as-types today.

4 Likes

Agreed, let's leave that out

Yes, that is exactly what I meant. It wouldn't be convenient and correct in terms of generality at all for a stdlib protocol hierarchy such as Numeric or Sequence to be designed using universally quantified protocols (<T>), including because T<all associated types> is inconvenient as you mentioned.

However, there are constructs that can't be properly modeled using only existential quantification (associated types and Self). An example is a generic functor or monad. On the other hand, generic parameters have less refinement possibilities (I assume it would be so looking at how generics currently work).

The bottom line, then again, is that, if they were to be introduced, generic protocols are a tool with a different set of advantages and disadvantages and, although they can be considered sugar at the compiler implementation level since they don't impact the runtime model, they have to coexist and be able work in conjunction with associated types and Self. In other words, in the type system, <T> shouldn't be regarded as sugar for associated types and Self. That is my reasoning.

Whilst I agree with the proposal, if the change is too much to have two separate kinds of things, perhaps it would be easier to require an additional keyword to treat a protocol as a type rather than a constraint. Rust recently did something similar with dyn Trait.

func foo<T: AProtocol>(_ t: T) { /* as currently */ }
func bar(_ t: dynamic AProtocol) { /* or whatever keyword */ }