Allowing cases of an enum to stand alongside cases of another type in a switch expression?

Brian Goetz brian.goetz at oracle.com
Mon Oct 17 19:55:11 UTC 2022


Short answer: we understand the problem, and it will eventually be 
addressed.

Switch started with a lot of baggage, and accreted more over the years.  
Fully rehabilitating switch will take some time. Support for enums in 
switches remains, currently, in the domain of "legacy behavior"; to 
complete the rehabilitation we'll need to interpret constant case labels 
as constant _patterns_.  This is on the list, and we're working through 
it, but we haven't gotten there yet.

Fans of algebraic data types in Haskell will feel this gap, in Haskell's 
version of enums and records are the same thing:

     data List t = Nil | Cons t (List t)

Nil is like an enum; Cons is like a record.  It is perfectly reasonable 
to switch over something that has both, we're just not there yet.

On 10/16/2022 10:54 PM, David Alayachew wrote:
> Hello Amber Dev Team,
>
> Currently in Java, the following is permitted.
>
>    sealed interface SealedInterface {}
>    record TypeA() implements SealedInterface {}
>    record TypeB() implements SealedInterface {}
>
>    private void 
> mixingEnumsUnderTheSameSealedInterfaceDuringAPatternMatch()
>    {
>
>       final SealedInterface someValue = new TypeA();
>
>       final int result =
>          switch (someValue)
>          {
>
>             case TypeA a -> 1;
>             case TypeB b -> 2;
>
>          };
>
>    }
>
> However, the following is not permitted.
>
>
>    sealed interface SealedInterface {}
>    record TypeA() implements SealedInterface {}
>    enum TypeB implements SealedInterface { B; }
>
>    private void 
> mixingEnumsUnderTheSameSealedInterfaceDuringAPatternMatch()
>    {
>
>       final SealedInterface someValue = new TypeA();
>
>       final int result =
>          switch (someValue)
>          {
>
>             case TypeA a -> 1;
>             case TypeB.B -> 2;
>
>          };
>
>    }
>
> The following is also not permitted.
>
>
>    sealed interface SealedInterface {}
>    enum TypeA implements SealedInterface { A; }
>    enum TypeB implements SealedInterface { B; }
>
>    private void 
> mixingEnumsUnderTheSameSealedInterfaceDuringAPatternMatch()
>    {
>
>       final SealedInterface someValue = TypeA.A;
>
>       final int result =
>          switch (someValue)
>          {
>
>             case TypeA.A -> 1;
>             case TypeB.B -> 2;
>
>          };
>
>    }
>
> In both of the above failing cases, I receive the following message.
>
>    ScratchPad.java:132: error: constant expression required
>             case TypeA.A -> 1;
>                       ^
>
> It seems the only way to get rid of the error message is to split the 
> switch expression into 2 each time we see an enum and want to know its 
> value.
>
> Here is for the first failing example.
>
>
>    sealed interface SealedInterface {}
>    record TypeA() implements SealedInterface {}
>    enum TypeB implements SealedInterface { B; }
>
>    private void 
> mixingEnumsUnderTheSameSealedInterfaceDuringAPatternMatch()
>    {
>
>       final SealedInterface someValue = TypeB.B;
>
>       final int result =
>          switch (someValue)
>          {
>
>             case TypeA typeA -> 1;
>             case TypeB typeB ->
>                switch (typeB)
>                {
>                   case B -> 2;
>                };
>
>          };
>
>    }
>
> And here is for the second failing example.
>
>    sealed interface SealedInterface {}
>    enum TypeA implements SealedInterface { A; }
>    enum TypeB implements SealedInterface { B; }
>
>    private void 
> mixingEnumsUnderTheSameSealedInterfaceDuringAPatternMatch()
>    {
>
>       final SealedInterface someValue = TypeA.A;
>
>       final int result =
>          switch (someValue)
>          {
>
>             case TypeA typeA ->
>                switch (typeA)
>                {
>                   case A -> 1;
>                };
>             case TypeB typeB ->
>                switch (typeB)
>                {
>                   case B -> 2;
>                };
>
>          };
>
>    }
>
>
> I can understand the error message well enough -- an enum value is not 
> considered a constant expression.
>
> But the following code is not only permitted, but also exhaustive, 
> like my very first example.
>
>
>    enum TypeABC { A, B, C, ; }
>
>    private void 
> mixingEnumsUnderTheSameSealedInterfaceDuringAPatternMatch()
>    {
>
>       final TypeABC someValue = TypeABC.A;
>
>       final int result =
>          switch (someValue)
>          {
>
>             case A -> 1;
>             case B -> 2;
>             case C -> 3;
>
>          };
>
>    }
>
> I understand how switch expressions are the vehicle we are using to 
> deliver exhaustive pattern matching for objects. However, the fact 
> that 2 very different concepts are sharing the same vehicle (and have 
> to take turns being the driver) means that we end up in some 
> frustrating potholes.
>
> For starters, records and enums organically play well together. There 
> are some concepts best modeled by an enum, and others where a record 
> is a better choice. So if your domain of possible options encourages 
> you to use the 2 together, why does the language make it so hard to do 
> so when doing exhaustiveness checks?
>
> In some of the articles/videos/etc. about pattern matching by some of 
> the language designers (Brian Goetz's Data Oriented Programming 
> article, for example), we see a new pattern that involves using a 
> record with no fields as form of data representation.
>
> Consider this example.
>
>    sealed interface Division {}
>    record DivideByZero() implements Division {}
>    record DivisionResult(double answer) implements Division {}
>
> I understand and accept how, in this situation, a separate record type 
> under the same sealed interface is a perfectly acceptable solution to 
> model an irrepresentable state.
>
> However, because of the fact that switch expressions don't let me mix 
> enum value cases easily with record type cases, I find myself leaning 
> more towards record type cases as a complete replacement for enum 
> values. This is because I don't want to deal with the head ache of 
> trying to get exhaustiveness checking while trying to keep my problem 
> visible on one screen.
>
> And yet, the language as is seems to outright discourage enums for 
> switch expressions. After all, records can model everything enums can, 
> but enums can't model everything records can. And since switch 
> expressions make it easier to use records instead of enums, why use 
> enums at all?
>
> But that's a serious problem. Yes, a record can model data the same 
> way an enum can, but that doesn't mean that it is always a drop in 
> replacement, let alone a good one. There are instances where using a 
> class/record type as opposed to an enum value would be problematic at 
> best for your application.
>
> I will stop considering hypotheticals and simply ask -- am I missing 
> something? Is this something that we plan to solve later, but isn't a 
> priority now? Is this not even a problem at all and I am looking at 
> things the wrong way?
>
> Thank you for your time and help!
> David Alayachew
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20221017/0efd0dc3/attachment-0001.htm>


More information about the amber-dev mailing list