Totality at switch statements

Brian Goetz brian.goetz at oracle.com
Sun Jun 19 20:02:22 UTC 2022


> Please don't require default to be at the bottom.  There are some 
> situations where the default case can recover from unhandled input 
> with a satisfactory response. See the code below.

Yes, but such cases happen almost exclusively in conjunction with 
fallthrough, and case patterns can't fall through (because then the 
bindings might not be DA.)   Right now, we make a distinction between 
"legacy switch" (old types, constant case labels, no patterns) and other 
switches (expressions, or statements with patterns.)  We have to carve 
out the code you cite below for compatibility reasons, but we don't have 
to let it infect the new world.

The things we know from "old switch" are not necessarily representative 
of the new world....

> The default case logs the problem and falls through to return false.  
> If the "NO" case were more complex and if default were required at the 
> bottom, then the requirement would cause duplicate code.  In some 
> situations, the duplicate code could be extracted to a separate 
> method.  In other situations, there are too many local variables being 
> changed by the case in a loop to make it possible.
>
> public static boolean isYes(String value)
> {
>    switch (value)
>    {
>       case "YES":
>          return true;
>
>       default:
>          LOGGER.error("Unhandled value: {}", value);
>          // break;
>
>       case "NO":
>          return false;
>    }
> }
>
> On Sun, Jun 19, 2022 at 12:27 PM Brian Goetz <brian.goetz at oracle.com> 
> wrote:
>
>
>>     As always, your answers are well thought out and have good
>>     points.  I wonder if it would make sense to write up a design
>>     page, document the choices and why one choice was picked over
>>     another.  Then, have your team review it and then the world via
>>     JEP.  This way future and other language designers can benefit
>>     from the information.  If some future designer wants to change
>>     the language, they can read the document and realize all the
>>     constraints and hidden pits.
>
>     We did something like that for a few of the features (see the FAQ
>     / style guides Stuart Marks put together for Local Variable Type
>     Inference, and for Text Blocks.)  It would be great to have them
>     for all the features, and keep them updated as new questions come
>     up and get answered here.  In a perfect world, we would!  But
>     sometimes there are too many things to do.  We'd welcome
>     contributions on this.
>
>>     I love that Java will require exhaustiveness in switch and
>>     provide the feature for more data types.  It will do one of two
>>     things.  I can either put all the cases and know that all
>>     situations are handled, OR I can add a default and add error
>>     handling for an unexpected situation.  It will help me write more
>>     robust code without having to write as many unit tests.  As you
>>     pointed out, I will spend less time debugging and furthermore
>>     writing a unit test to reproduce the problem.
>
>     Exactly!  And another benefit is that someone reading the code can
>     tell whether you intended the switch to cover all the cases, or
>     not, just by looking for a default / total case.  (Right now the
>     positioning of `default` is unconstrained, as it always was, but
>     we may require it float to the bottom, to make it easier to find
>     the catch-all case.)
>
>     A subtle point about exhaustiveness that people don't often
>     realize is that, when switching over a domain with exhaustiveness
>     information (enums or sealed types), it is better *not* to have a
>     default, and instead let the compiler insert a throwing default to
>     catch separate compilation errors.  Because then, if someone adds
>     a new enum constant or subtype later, you find out next compile
>     time, rather than at run time.
>
>     (Which reminds me of another thing to add to my response to Hunor
>     on the topic: because we add a throwing default to total switches
>     that don't already have a catch-all case, this would be yet
>     another subtle and surprising difference between switch
>     expressions and switch statements.)
>
>>
>>     On Sun, Jun 19, 2022 at 7:42 AM Brian Goetz
>>     <brian.goetz at oracle.com> wrote:
>>
>>
>>         > I haven't played with switch expressions, but I think of
>>         them kind of
>>         > like this but much more performant...
>>         >
>>         > int y = x == 0 ? 0 : x == 1 ? 2 : x == 2 ? 4 : x == 3 ? 6;
>>
>>         I encourage you to broaden how you think of this. Yes, they
>>         might be
>>         more performant (though they might not be -- a good compiler
>>         can chew
>>         this up too), but that is is both a secondary, and a dependent,
>>         benefit.  The alternative is:
>>
>>              int y = switch (x) {
>>                  case 0 -> 0;
>>                  case 1 -> 2;
>>                  case 2 -> 4;
>>                  default -> 6;
>>              }
>>
>>         which I'm sure everyone finds more readable.
>>
>>         The primary benefit is that you are using a simpler, more
>>         constrained
>>         concept.  A chain of ternaries or if-else can have arbitrary and
>>         unrelated predicates, and offers less opportunity for
>>         exhaustiveness
>>         checking.  It involves unneeded repetition ("x ==
>>         <something>") which is
>>         a place for errors to hide in.  The fact that each predicate
>>         in the
>>         chain above is of the form `x == <something>` is neither
>>         mandated nor
>>         checked nor even obvious at first glance; this makes it
>>         harder to read
>>         and more error-prone; you could easily fumble this for "z ==
>>         2" and it
>>         would be hard to notice.  Whereras a switch has a
>>         distinguished operand;
>>         you are saying "this operand should match one of these
>>         cases", and
>>         readers know it will match *exactly* one of those cases. 
>>         That is a more
>>         constrained statement, and by using a more specialized tool,
>>         you can
>>         make the calculation more readable and less error-prone.
>>
>>         The performance benefit, if there is one, comes from the fact
>>         that you
>>         have performed a semantic "strength reduction", which is
>>         potentially
>>         more optimizable.  But that's a totally dependent benefit,
>>         one which
>>         flows from having written more clear, specific code in the
>>         first place.
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20220619/a1d29c87/attachment-0001.htm>


More information about the amber-dev mailing list