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

David Alayachew davidalayachew at gmail.com
Mon Oct 17 02:54:14 UTC 2022


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/20221016/4ebe7d76/attachment.htm>


More information about the amber-dev mailing list