Totality at switch statements

Nathan Reynolds numeralnathan at gmail.com
Sun Jun 19 20:05:09 UTC 2022


Hmm... it seems you have some deep insights into the new switch that I have
yet to learn.  This is understandable since I haven't played with the new
switch yet.  I hope to move to Java 17 in a few months and then stay on top
of every Java release once CI/CD is in better shape.

On Sun, Jun 19, 2022 at 2:02 PM Brian Goetz <brian.goetz at oracle.com> wrote:

>
> 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/d53a29cc/attachment.htm>


More information about the amber-dev mailing list