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