I think both any P
and _ & P
are not sufficient. They are useful as a convenience shortcut for common cases, but not sufficient to cover all the cases.
Consider the example from the proposal:
extension Sequence {
public func enumerated() -> EnumeratedSequence<Self> {
return EnumeratedSequence(_base: self)
}
}
let s: any Sequence = [1, 2, 3]
let e = s.enumerated()
Even after SE-0309 this does not type check, but in theory it is possible to lift restrictions further. Then to express the type of e
we would need something like any<T> EnumeratedSequence<T> where T: Sequence
. For this specific example EnumeratedSequence<_ & Sequence>
would work too, but in general case T
may occur inside the type multiple times.
For example:
protocol P {}
extension Int: P {}
extension String: P {}
struct KeyWrapper<T: P>: Hashable {}
struct ValueWrapper<T: P> {}
let a: Dictionary<KeyWrapper<Int>, ValueWrapper<Int>> = ...
let b: Dictionary<KeyWrapper<String>, ValueWrapper<String>> = ...
let x: Array<any<T> Dictionary<KeyWrapper<T>, ValueWrapper<T>> where T: P> = [a, b]
Alternatively, this can be expressed in a top-down fashion using constraints if we can refer to generic parameters as associated types:
...
let x: [any Dictionary where Key == some KeyWrapper, Value == some ValueWrapper, Key.Wrapped == Value.Wrapped, Key.Wrapped: P] = [a, b]
See Syntax for existential type as a box for generic type for more.
This will become even more import once Swift gets unboxing existential. Unboxing gives a generic type of local scope, and to escape this scope, any value needs to either have a non-generic type, or be wrapped into an existential container.