No it is not. Please see the example P
I gave in this post, along with a complete explanation of why the existential type P
can never support the API required by protocol P
.
they will be drawn toward the syntactically lightweight use, which I think is more often inappropriate.
I would argue that points to a flaw in the language that should be solved
I agree; it should be solved if possible, but…
rather than artificially imposing pain on users because we think we know better.
…the pain is not artificial; AFAICT it's inherent, as I hope my example shows.
It doesn't go nearly far enough.
I agree again. We should take a good look at the actual use cases that create what you call a “mile-high wall of overhanging granite” and design language features (or write educational articles, if that is more appropriate to our analysis) that address those needs. Incrementally chipping away at the restrictions on existentials does not necessarily seem like it leads to a good answer, and in the meantime, as I have mentioned elsewhere, just doing that could leave us with some serious new problems.
IMHO these concepts are impossible for mortal programmers to grasp because the bar for working with them is so high.
I don't think so. The problem is simple: we've created a confusing language feature by making the creation of existential types implicit. There are three sources of confusion that I know of:
- Sometimes when you declare a protocol, you also get an existential type that you can use to handle instances of any conforming type. Sometimes, though, depending on details of how you declared the protocol's interface, you don't get an existential type.
- Even when you do get an existential type, it doesn't conform to the protocol.
- Also, some parts of the protocol's API may be unavailable on the existential type (rare today, but true for extension methods with
Self
arguments).
Generalizing existentials in the way proposed means that the compiler would no longer bite you right away when you try to use an existential type; hooray! But instead, the compiler will bite you when you try to use the parts of the API that today are preventing us from creating the existential type. That takes away confusion #1 but compounds confusion #3. That could be a much worse place to be than the situation we have today for reasons cited in my first post.
I'm also not sold on the idea that existentials must impose a performance cost; maybe static compilation makes some form of specialization for an existential (aka non-generic) function impossible but is that true for all cases or just some (If we were to admit JIT ala JS then it is definitely possible to provide dynamic specializations of a function, switched based on argument type). I'm not saying Swift could, would, or should do this, just muttering questions out loud.
Just some: when the compiler can “see” all the types involved, it can use the information (e.g. knowledge that two types must be the same) to optimize code just as well as if that constraint were captured in a generic. But that can only happen under special conditions and trying to broaden the cases that get optimized usually requires optimizer heroics (a big development investment) and increased compile time (which is bad for end users). To be fair, most cases with resilient generics will not optimize well, either.
So, yes: using existentials where you could use generic constraints implies a performance cost in the general case. Applied without careful discrimination over the majority of Swift programs, it cannot help but be significant. (And, yes, Swift should probably have a JIT)
I expect the addition of opaque result types to satisfy the majority of needs for which people want to use existentials today
It might solve many of the cases where protocol methods return Self
but that is only a subset (I'll admit I don't know how big that subset is in realistic codebases; maybe it is far larger than I think?).
? I don't know of any special applicability to protocol methods that return Self
. Opaque result types cover all cases where an existential would be used purely to avoid exposing the actual type of a return value as API.