Switch on sealed enums
Brian Goetz
brian.goetz at oracle.com
Wed Jan 12 14:54:32 UTC 2022
(delayed response)
Your issue (1) is most of the problem; enum switches have always been
special, and so accomodating enums in general switches will be some more
work. But, once we do so, there is no major impediment to saying the
merged switch covers Token; coverage on a sealed type is derived from
coverage on all the permitted subtypes.
Still, I think this is a lower priority than a number of other
pattern-related things; it makes up for some of the shortcomings of
enums, but still leaves a lot of them on the table.
On 11/24/2021 3:23 AM, Matthias Perktold - ASA wrote:
> I've played around with sealed interfaces implemented by multiple enums only, and I've found some inconveniences I wanted to share here.
>
> Consider the following class hierarchy:
>
> sealed interface Token {
> enum Digit implements Token { ONE, TWO, THREE }
> enum Letter implements Token { A, B, C }
> }
>
> I can use a nested switch expression to cover all cases:
>
> static String toStringNested(Token token) {
> return switch (token) {
> case Digit d -> switch (d) {
> case ONE -> "1";
> case TWO -> "2";
> case THREE -> "3";
> };
> case Letter l -> switch (l) {
> case A -> "A";
> case B -> "B";
> case C -> "C";
> };
> };
> }
>
> I thought that in theory, I should be able to use a single, flattened switch that covers all digits and all letters:
>
> static String toStringFlat(Token token) {
> return switch (token) {
> case ONE -> "1";
> case TWO -> "2";
> case THREE -> "3";
> case A -> "A";
> case B -> "B";
> case C -> "C";
> };
> }
>
> This is probably deliberately not supported (yet). Anyway, here are my findings:
>
> 1. The compiler requires that the enum constants be unqualified.
> But since we have multiple enums here, just leaving the enum away doesn't work either; we need to add static imports:
>
> import static package.to.Token.Digit.*;
> import static package.to.Token.Letter.*;
>
> 2. The compiler cannot figure out that the switch is exhaustive, even if we do cover all cases.
> We need to add a default case to fix this compiler error.
>
> 3. With the default case added, the code still doesn't compile (my IDE thinks it does);
> We get an error "constant expression required" for each case.
>
>
> In case you wonder why one would use sealed hierarchies of enums, it probably makes more sense to think about these
> as "enums with subgroups". Using this approach, you can model more precisely that a method accepts or returns only a subset
> of the cases, but a logical coherent subset.
> One special reason of grouping enum constants is the use of generics: we could extend our hierarchy as follows:
>
> sealed interface Token<T> {
> T value();
> enum Digit implements Token<Integer> {
> ONE, TWO, THREE;
> @Override public Integer value() { ... }
> }
> enum Letter implements Token<Character> {
> A, B, C;
> @Override public Character value() { ... }
> }
> }
>
> This could also be seen as a workaround for JEP 301: "Enhanced Enums", but a very good one in my opinion.
> Only when every enum constant needs a different type argument (as in the JEP's examples), you would need to wrap each constant
> in its own enum.
>
> Overall, I think there are good places for such groupings of enums. And it would help if they could be used just like a single enum inside
> switch.
>
>
> Cheers,
> Matthias Perktold
>
More information about the amber-dev
mailing list