It’s not obvious to me that allowing exhaustive switches over ordinary private/internal protocols is really a good language design. It’s generally not a source-breaking change to make a declaration more accessible; and that’s a very good thing, because reorganizing a module to turn some of its internal abstractions into external ones (perhaps by splitting the module) is a natural code-evolution step.
Anyway, your proposal is very long and changes a lot of things, so I’m afraid that my responses will be relatively high-level, and I’m sorry if you’ve already considered some of these points. In general, I think this is a very interesting idea, but it’s a very significant shift to the language model, and I’m concerned about some of the implications.
1. This doesn’t really simplify the fundamental type system. Enums are a kind of possibly-generic nominal type that lack subtype conversions; in this sense, they exactly mirror structs. We could have a hundred different formal kinds of possibly-generic nominal types that lack subtype conversions and it would all be basically the same to the fundamental type system.
2. There are subsidiary features required for the convenience of enums, like exhaustiveness checking. These features still exist in your proposal. So still no simplification.
3. Since the enum form will still exist in the language, and since the design patterns using it will typically be quite different from those for protocols, it’s not really a simplification for the programming model, either.
4. Generic enums will become completely unusable unless we lift the associated-type restrictions on existentials. This is something we’re interested in doing anyway, but it’s worth noting that it’s a prerequisite for this proposal.
5. Even if (4) is addressed, generic enums will take a massive usability hit unless we find a more convenient syntax for existential-type-with-specified-associated-type (e.g. Collection<T>). Again, this is something we may be interested in anyway. This, however, imposes specific constraints on the general solution that we might be less comfortable with.
6. Your proposal subtly adds a major new expressive power to protocols. In particular, things like CompassPoint.North mean that there are now concrete declarations inside protocols, not just protocol requirements.
7. The change to the inferred type of CompassPoint.North seems pretty bad to me. Consider a “var” instead of a “let”.
8. Types normally cause the compiler to emit a fair amount of global metadata. I’m concerned about spitting out a ton of basically useless metadata for, say, a C-like enum with twenty cases.
9. Enums have specialized representations in memory which only allocate space for the enumerated cases; let’s call this a “tagged-sum” representation, as opposed to the standard “opaque” representation used by existentials. Implicitly using a tagged-sum representation for a sealed protocol requires a lot of heroism from the compiler, and this could become a compile-time performance bottleneck and, in general, a major obstacle for incremental compilation.
10. It is unclear to me what happens to the tagged-sum representation in, say, a protocol composition type. If the answer is to use a representation which only includes the intersection of the protocols, that implies the use of potentially expensive dynamic representation changes when performing a subtype conversion between different existential types. That conversion is something that can happen dynamically due to dynamic casts in generic code.
On Dec 15, 2015, at 6:50 AM, Marc Knaup via swift-evolution <email@example.com> wrote:
Thank you for your extensive feedback.
The way I proposed it is actually the other way round:
Switches over enumerations still don't need a default case since all cases (i.e. types conforming to them) are known at compile-time.
The same benefit will become available also to other protocols. If a protocol is not public (or not publicly conformable) then the compiler again knows all types explicitly declaring conformance to that protocol at compile-time and the developer can omit a default case when switching over instances of that protocol.
So the behavior of Swift 2.1 regarding switches and default cases for enumerations remains the same while for many protocols the compiler can additionally issue a warning that the default case is unreachable and thus unnecessary.