I'm overall in favor of segregating protocols from their existential types as proposed above. However I think some alterations would be integral. Thus, I am going to propose some more changes, which - to be clear - I do think are very hard to implement in the real world.
So what do I propose?
I think the proposed way to refer to a protocol existential types (by parameterizing Any
) is quite good. The syntax is clear and easy to use - yet not too lightweight - so that people don't mindlessly resort to Existentials in cases where Generics would be better. However, this syntax also poses a lot of problems and has drawn criticism over its odd parameterization behavior:
let foo: Array<Int>
// Generics Syntax
let bar: Any<Error>
// Existentials Syntax
let baz: Any
// What happened here?
// `Array<Element = Int>`
// is currently invalid,
// so what's this
So to tackle this problem, I propose that we add a supertype "Value" for what is currently "Any"- thus improving the parameterization behavior of the proposal. This way, we would separate the hypothetical supertype Value
from the existential-related Any
. Furthermore, potential future syntax - as discussed in Improving the UI of Generics - would be improved :
func foo(bar: some Value) {}
// equivalent to:
func foo<Value>(bar: Value) {}
Moreover, a clearer distinction between Generics, Existentials and the super-type Value
would be established:
protocol Value {}
// Every type implicitly
// conforms to `Value`.
let foo: Value
// Since `Value` would
// essentially be a protocol
// we cannot bound `foo` to
// the `Value` itself, but rather
// its Existential.
❌ Cannot use `Value` as a type.
Did you mean `Any<Value>`?
Also, many have expressed their concerns about how parameterization would interact with other non-protocol types. Personally, I see that not as a constraint, but rather as a push towards generalized existentials which would support protocols as well structs/classes/enums:
let foo: Any<Array>
let foo: Any<Int>
⚠️ `Any<Int>` is equivalent to `Int`.
let foo: Any<Any<Array>>
// Since `Any<Int>` is a
// concrete type, its
// Existential would not serve
// any purpose. It's like the
// above example.
⚠️ `Any<Any<Array>>` is equivalent to `Any<Array>`.
Why Not Use an "any" Modifier?
In my opinion, an "any" modifier (written as such: any Error
) would be too lightweight to use for Existentials. That's because despite being great for certain situations, Generics are often a better choice. I'm concerned that by adopting the "lightweight" syntax many beginners would be unable to differentiate between the two and, as a result, instinctively choose the former over the latter. Not to mention, that Existential Types are just that: Types. That is, despite their boxing behavior and syntactic magic they'll still largely behave as Types in future language versions (where they'd be extensible).