JEP 441: Pattern Matching for switch (suggestion re: handling sealed type selector expressions)

Brian Goetz brian.goetz at oracle.com
Sun Apr 9 18:18:49 UTC 2023


I understand why you were motivated to make this suggestion.  But, I think the restriction you propose is overly restrictive.

First let me amplify on your motivation, as this was an important motivation in the design of this feature: a switch that is exhaustive without a catch-all case is better than a switch that is exhaustive with one.  This is not because you don’t have to write the catch all case when you cover all the cases, though that is a nice side benefit.  It is because you get _better type checking_.  The compiler verifies your intent of exhaustiveness, and triggers an error if this is undermined by later additions to an enum or sealed class.  A default sweeps the sin under the rug.

So, you say, if better type checking is better, why not force everyone to get that better type checking by being exhaustive the honest way?  This is where things start to go off; there are too many legitimate use cases that this excludes.

First, consider the case where you have a large set of choices (such as tokens in a parser), but you only want to act on a few of them.  Switch is a good tool for this:

    status = switch (nextToken) {
        case OPEN_PAREN -> …
        case IDENTIFIER -> …
        default -> throw new ParseException(“Expecting identifier or parentheses”);
    }

This code is fine!  It is clear what is going on — we’re expecting one of two tokens, and anything else is an error.  Is the user helped by being required to list the other 72 tokens explicitly instead of a catch-all?  Is the code made more readable?

Another example is the common pattern of a switch with two clauses, which is often more appropriate than an if:

    boolean shouldStop = switch (color) {
        case RED -> true;
        default -> false;
    }

Yes, this could be a ternary, but this pattern is pretty useful and pretty clear, and ternaries don’t scale.  Having to list out YELLOW and GREEN in the second case, even though there are only two, would not be an improvement.

I think what tripped you up was you extrapolated from a set of examples where you really were handling all, or nearly all, of the cases.  But switches are useful when you only want to handle one or two cases specially too.


On Apr 8, 2023, at 10:09 AM, Robert <roberts14 at cablelink.at<mailto:roberts14 at cablelink.at>> wrote:

Hi all, newbie here.

First off — loving Amber features.

Now, a *very narrow* observation about how JDK 20 handles sealed type selector expressions.
(Code snippet below.)

If a sealed type S later grows its list of permitted subtypes at some point,
how should the compiler handle switch blocks with S as selector?

In the absence of a catchall label (i.e. “case default -> ...” or “case S s -> ...”) ,
the compiler already correctly issues an error that coverage is incomplete.

But if the block ended with a catchall label, the compiler is silent; the best that
can be hoped-for is that the coder throws a defensive exception in the catchall label
(and hope that it is triggered in testing).

Suggestion:

Compiler should *disallows* catchall statements in switch blocks that use a sealed type
in the switch selector expression.  Note that this special case does not affect all the
other non-sealed-type selectors (e.g. Object o), and appears (to me) to strengthen the
safety-value of sealed types in this particular scenario (similar to enums).

All in Java’s spirit of “least surprise”.

Cheers,
Robert


PS. reported behavior confirmed using jshell in JDK 20:

jshell --enable-preview
|  Welcome to JShell -- Version 20
|  For an introduction type: /help intro

on the following:

sealed interface S permits S.X, S.Y, S.Z, S.NEWBIE {

final class X implements S {};

final class Y implements S {};

final class Z implements S {};

final class NEWBIE implements S {};

public static void main(String [] sa) {

   S something = new NEWBIE();
   System.out.println(
switch (something) {
case null -> 0;
        case X x -> 1;
        case Y y -> 2;
        case Z z -> 3;
case S s -> -1;
// case default -> -1;
});
    };
}


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230409/859643c7/attachment-0001.htm>


More information about the amber-dev mailing list