<div dir="ltr">It's nice to see this: I think it helps some with the previous discussion on the list about "why do we want instanceof for primitives?" The point isn't that we expect anyone to use instanceof for primitives often, but that conversions for primitives in patterns is an important part of fixing up the asymmetries switch is still stuck with.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Sep 12, 2022 at 12:36 PM Brian Goetz <<a href="mailto:brian.goetz@oracle.com">brian.goetz@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">

  
  <div>
    <font size="4"><font face="monospace">The work on primitive patterns
        continues to yield fruit; it points us to a principled way of
        dealing with _constant patterns_, both as nested patterns, and
        to redefining constant case labels as simple patterns.  It also
        points us to a way to bring the missing three types into the
        realm of switch (since now switch is usable at every type _but_
        these): float, double, and boolean.  While I'm not in a hurry to
        prioritize this immediately, I wanted to connect the dots to how
        primitive type patterns lay the foundation for these two
        vestiges of legacy switch.  (The remaining vestige, not yet
        dealt with, is that legacy statement switches are not
        exhaustive.  We'd like a path back to uniformity there as well,
        but this is likely a longer road.)  <br>
        <br>
        **Constant patterns.**  In early explorations (e.g., "Pattern
        Matching Semantics"), we struggled with the meaning of constant
        patterns, specifically with conversions in the absence of a
        sharp type for the match target.  The exploration of that
        document treated boxing conversions but not other conversions,
        which would have created a gratuitously new conversion context. 
        This was one of several reasons we deferred constant patterns. 
        <br>
        <br>
        The current status is that constant case labels (e.g., `case 3`)
        are permitted (a) only in the presence of a compatible operand
        type and (b) are not patterns.  This has led to some accidental
        complexity in specifying switch, since we can have a mix of
        pattern and non-pattern labels, and it means we can't use
        constants as nested patterns.  (We've also not yet integrated
        enum cases into the exhaustiveness analysis in the presence of a
        sealed type that permits an enum type.)  Ret-conning all case
        labels as patterns seems attractive if we can make the semantics
        clear, as not only does it bring more uniformity, but it means
        we can use them as nested patterns, not just at the top level of
        the switch.  More composition.  <br>
        <br>
        The recent work on `instanceof` involving primitives offers a
        clear and principled meaning to `0` as a pattern; given a
        constant `c` of type `C`, treat <br>
        <br>
            x matches c<br>
        <br>
        as meaning <br>
        <br>
            x matches C alpha && alpha eq c<br>
        <br>
        where `eq` is a suitable comparison predicate for the type C (==
        for integral types and enums, .equals() for String, and
        something irritating for floating point.)  This gives us a solid
        basis for interpreting something like `case 3L`; we match if the
        target would match `long alpha` and `alpha == 3L`.  No new
        rules; all conversions are handled through the type pattern for
        the static type of the constant in question.  Not
        coincidentally, the rules for primitive type patterns support
        the implicit conversions allowed in today's switches on `short`,
        `byte`, and `char`, which are allowed to use `int` labels,
        preserving the meaning of existing code while we generalize what
        switch means.  <br>
        <br>
        The other attributes of patterns -- applicability,
        exhaustiveness, and dominance -- are also easy:<br>
        <br>
         - a constant pattern for `c : C` is applicable to S if a type
        pattern for `C` is applicable to S.  <br>
         - a type pattern for T dominates a constant pattern for `c : C`
        if the type pattern for T dominates a type pattern for C.<br>
         - constant patterns are never exhaustive.  <br>
        <br>
        No new rules; just appeal to type patterns.  <br>
        <br>
        **Switch on float, double, and boolean.**  Switches on floating
        point were left out for the obvious reason -- it just isn't that
        useful, and it would have introduced new complexity into the
        specification of switch.  Similarly, boolean was left out
        because we have "if" statements.  In the original world, where
        you could switch on only five types, this was a sensible
        compromise.  We later added in String and enum types, which were
        sensible additions.   But now we move into a world where we can
        switch on every type _except_ float, double, and boolean -- and
        this no long seems sensible.  It still may not be something
        people will use often, but a key driver of the redesign of
        switch has been refactorability, and we currently don't have a
        story for refactoring<br>
        <br>
            record R(float f) { }<br>
        <br>
            switch (r) { <br>
                case R(0f): ...<br>
                case R(1f): ...<br>
            }<br>
        <br>
        to<br>
        <br>
            switch (r) { <br>
                case R rr: <br>
                    switch (rr.f()) { <br>
                        case 0f: ...<br>
                        case 1f: ...<br>
                    }<br>
            }<br>
        <br>
        because we don't have switches on float.  By retconning constant
        case labels as patterns, we don't have to define new semantics
        for switching on these types or for constant labels of these
        types, we only have to remove the restrictions about what types
        you can switch on.  <br>
        <br>
        **Denoting constant patterns.**  One of the remaining questions
        is how we denote constant patterns.  This is a bit of a
        bikeshed, which we can come back to when we're ready to move
        forward.  For purposes of exposition we'll use the constant
        literal here.  <br>
        <br>
        **Closing a compositional asymmetry.**  In the "Patterns in the
        Java Object Model" document, we called attention to a glaring
        problem in API design, where it becomes nearly impossible to use
        the same sort of composition for taking apart objects that we
        use for putting them together.  As an example, suppose we
        compose an `Optional<Shape>` as follows: <br>
        <br>
            Optional<Shape> os = Optional.of(Shape.redBall(1));<br>
        <br>
        Here, we have static factories for both Optional and Shape, they
        don't know about each other, but we can compose them just fine. 
        Today, if we want to reverse that -- ask whether an
        `Optional<Shape>` contains a red ball of size 1, we have
        to do something awful and error prone:<br>
        <br>
            Shape s = os.orElse(null);<br>
            boolean isRedUnitBall = s != null<br>
                                   && s.isBall()<br>
                                   && (s.color() == RED)<br>
                                   && s.size() == 1;<br>
            if (isRedUnitBall) { ... }<br>
        <br>
        These code snippets look nothing alike, making reversal harder
        and more error-prone, and it gets worse the deeper you compose. 
        With destructuring patterns, this gets much better and more like
        the creation expression: <br>
        <br>
            if (os instanceof Optional.of(Shape.redBall(var size))<br>
                && size == 1) { ... }<br>
        <br>
        but that `&& size == 1` was a pesky asymmetry.  With
        constant patterns (modulo syntax), we can complete the
        transformation:<br>
      </font></font><br>
    <font size="4"><font face="monospace"><font size="4"><font face="monospace">    if (os instanceof
            Optional.of(Shape.redBall(1)) { ... }<br>
            <br>
            and destructuring looks just like the aggregation.<br>
            <br>
            **Bonus round: the last (?) vestige.**  Currently, we allow
            statement switches on legacy switch types (integers, their
            boxes, strings, and enums) with all constant labels to be
            partial, and require all other switches to be total. 
            Patching this hole is harder, since there is lots of legacy
            code today that depends on this partiality.  There are a few
            things we can do to pave the way forward here:<br>
            <br>
             - Allow `default -> ;` in addition to `default -> {
            }`, since people seem to have a hard time discovering the
            latter.  <br>
             - Issue a warning when a legacy switch construct is not
            exhaustive.  This can start as a lint warning, move up to a
            regular warning over time, then a mandatory (unsuppressable)
            warning.  Maybe in a decade it can become an error, but we
            can start paving the way sooner.<br>
            <br>
            <br>
          </font></font></font></font>
  </div>

</blockquote></div>