<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div><br></div><hr id="zwchr" data-marker="__DIVIDER__"><div data-marker="__HEADERS__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>De: </b>"Brian Goetz" <brian.goetz@oracle.com><br><b>À: </b>"amber-spec-experts" <amber-spec-experts@openjdk.java.net><br><b>Envoyé: </b>Mercredi 19 Mai 2021 13:12:43<br><b>Objet: </b>Re: Rehabilitating switch -- a scorecard<br></blockquote></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><font size="+1"><font face="monospace">So, here's another aspect of
        switches rehabilitation, this time in terms of syntactic
        rewrites.  By way of analogy with lambdas, there's a sequence of<br><br></font></font><br>
    <font size="+1"><font face="monospace"><font size="+1"><font face="monospace">    x -> e                 // parens
            elided in unary lambda<br><br>
            is-shorthand-for<br></font></font></font></font><font size="+1"><font face="monospace"><font size="+1"><font face="monospace"><font size="+1"><font face="monospace"><br>
                    (x) -> e               // types elided <br><br>
                is-shorthand-for<br><br></font></font></font></font>    (var x) -> e          
        // explicit request for inference<br><br>
        is-shorthand-for<br><br>
            (<actual type> x) -> e // explicit types<br><br>
        That is, there is a canonical (lowest) form, and the various
        shorthands form a chain of embeddings.  The chain shape reduces
        cognitive load on the user, because instead of thinking "there
        are seven forms of lambda", they can instead think there is
        single canonical form, with progressive options for leaving
        things out / mushing things together.  <br><br>
        We get more of a funnel with the syntax of switch: <br><br>
            case L, J, K -> X;<br><br>
        is-shorthand-for<br><br></font></font><font size="+1"><font face="monospace"><font size="+1"><font face="monospace">    case L, J, K: yield X;  
            // expression switch, X is an expression<br></font></font></font></font><font size="+1"><font face="monospace"><font size="+1"><font face="monospace">    case
            L, J, K: X;         // expression switch, X is a block<br></font></font></font></font><font size="+1"><font face="monospace"><font size="+1"><font face="monospace"><font size="+1"><font face="monospace">    case L, J, K: X;
                break;  // statement switch<br><br></font></font></font></font>and <br><br></font></font><font size="+1"><font face="monospace"><font size="+1"><font face="monospace">    case L, J, K: X;<br><br>
            is-shorthand-for<br><br>
                 case L:<br>
                 case J: <br>
                 case K:<br>
                     X;</font></font></font></font></blockquote><div><br></div><div>We also have the inverse problem, rehabilitating lambda syntax to be aligned with the switch syntax.<br data-mce-bogus="1"></div><div>The only discempancy i'm aware of is <br data-mce-bogus="1"></div><div>   case Foo -> throw ...<br data-mce-bogus="1"></div><div>being allowed while<br data-mce-bogus="1"></div><div>  (Foo foo) -> throw ....<br data-mce-bogus="1"></div><div>is not.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>BTW, this year i've presented the switch expression before the lambda, so a student ask me why Java does not allow colon in lambda,</div><div>like this<br data-mce-bogus="1"></div><div>  (a, b) :</div><div>     X</div><div>     break;<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>instead of<br data-mce-bogus="1"></div><div>  (a, b) -> {<br data-mce-bogus="1"></div><div>    X</div><div>  }<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>I answered explaining that a lambda was a function not a block of instructions, but I still feel a diffuse guilt about the reuse of -> inside the switch.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Rémi<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><font size="+1"><font face="monospace"><font size="+1"><font face="monospace"><br><br></font></font></font></font><br>
    <div class="moz-cite-prefix">On 5/17/2021 5:36 PM, Brian Goetz
      wrote:<br>
    </div>
    <blockquote cite="mid:23f3db2e-3106-98ea-ba8a-27074102fc3c@oracle.com">
      
      <font size="+1"><font face="monospace">This is a good time to look
          at the progress we've made with switch.  When we started
          looking at extending switch to support pattern matching (four
          years ago!) we identified a lot of challenges deriving from
          switch's C legacy, some of which is summarized here:<br><br><a class="moz-txt-link-freetext" href="http://cr.openjdk.java.net/~briangoetz/amber/switch-rehab.html" target="_blank">http://cr.openjdk.java.net/~briangoetz/amber/switch-rehab.html</a><br><br>
          We had two primary driving goals for improving switch:
          switches as expressions, and switches with patterns as
          labels.  In turn, these pushed on a number of other
          uncomfortable aspects of switch: fall through, totality,
          scoping, and null handling.<br><br>
          Initially, we were unsure we would be able to rehabilitate
          switch to support these new requirements without being forever
          bogged down by the mistakes of the past.  Bit by bit, we have
          chipped away at the negative aspects of switch, while
          respecting the existing code that depends on those aspects.  I
          think where we've landed is, in many ways, better than we
          could have initially hoped for.  <br><br>
          Throughout this exercise, there were periodic calls for "just
          toss it and invent something new" (which we sometimes called
          "snitch", shorthand for "new switch"*), and no shortage of
          people's attempts to design their ideal switch construct.  We
          resisted this line of attack, because we believed having two
          similar-but-different constructs living side by side would be
          more annoying (and confusing) to users than a rehabilitated,
          albeit more complex, construct.  <br><br>
          The first round of improvements came with expression
          switches.  This was the easy batch, because it didn't
          materially change the set of questions we could ask with
          switch, just the form in which we asked the question.  This
          brought the following improvements:<br><br>
           - Switches as expressions.  Many existing switch statements
          are in reality modeling expressions, in a more roundabout and
          less safe way.  Expressing it directly is simpler and less
          error-prone.  <br>
           - Checked totality.  The compiler enforces that a switch
          expression is exhaustive (because, expressions must be
          total).  In the case of enum switches, a switch that covers
          all the cases needs no default clause, and the compiler
          inserts an extra case to catch novel values and throw (ICCE)
          on them.  (Eventually the same will be true for switches on
          sealed classes as well.)<br>
           - A fallthrough-free option.  Switches now give us a choice
          between two styles of _switch blocks_, the old willy-nilly
          style, and the new single-consequent (arrow) style.  Switches
          that choose arrow-style need not reason about fallthrough.  <br><br>
          Unfortunately, it also brought a new asymmetry; switch
          expressions must be total (and you get enhanced type checking
          for this), but switch statements cannot be.  This is a shame,
          since the improved type checking for totality is one of the
          best things about the improvements in switch, as a switch that
          is total by virtue of actually covering all the cases acts as
          a tripwire against new enum constants / permitted subtypes
          being added later, rather than papering it over with a
          catch-all.  We explored several ways to explicitly add back
          totality checking, but this always felt like a hack, and
          requires the programmer to remember to ask for this checking. 
          <br><br>
          Our resolution here offers a path to true healing with minimal
          user impact, by (temporarily) carving out the semantic space
          of old statement switches.  A "legacy switch" is a statement
          switch on a numeric primitive or its box, enum, or string, and
          which contains no pattern labels (i.e., a statement switch
          that is valid today.)  Like expression switches, we will
          require non-legacy statement switches to be exhaustive, and
          warn on non-exhaustive legacy switches.  (To make the warning
          go away, just insert a "default: " or "default: break" at the
          bottom of the switch; not painful.)  After some time, we
          should be able to make this warning an error, which again is
          easy to mitigate with a single line.  In the end, all switch
          constructs will be total and type-checked for exhaustiveness,
          and once done, the notion of "legacy switch" can be
          garbage-collected.<br><br>
          Looking ahead to patterns in switch, we have several legacy
          considerations to navigate:<br><br>
           - Fallthrough and bindings.  While fallthrough is not
          inherently problematic (though the choice of
          fallthrough-by-default was unfortunate), if a case label
          introduces a pattern variable, then fallthrough to another
          case (at least one that doesn't introduce the same pattern
          variable with the same type) makes little sense, and such
          fallthrough has been outlawed.  <br>
           - Scoping.  The block of a switch is one big scope, rather
          than each case label group being its own scope.  (Again, one
          might call this a historical error, since there's little good
          that comes from this.)  With case labels introducing variable
          declarations, this could have been a big problem, if one case
          polluted later cases (forcing users to pick unique names for
          each binding in a switch statement), but flow scopoing solves
          that one.  <br>
           - Nulls.  In Java 1.0, switching over reference types was not
          permitted, so we didn't have to worry about this.  In Java 5,
          autoboxing and enums meant we could switch over some reference
          types, but for all of these, null was a "silly" value so we
          didn't care about NPEing on null.  In Java 7, when we added
          string switch, we could have conceivably allowed `case null`,
          but instead chose to follow the precedent set by Java 5.  But
          once we introduce switches over any type, with richer
          patterns, eagerly NPEing on null becomes much more
          problematic.  We've navigated this by say that switches can
          NPE on null if they have no nullable cases; nullable cases are
          those that explicitly say "null", and total patterns (which
          always come last since they dominate all others.)  The old
          rule of "switches throw on null" becomes "switches throw on
          null, except when they say 'case null' or the bottom case is
          total."  Default continues to mean what it always did --
          "anything not already matched, except null."<br><br>
          The new treatment of null actually would have fallen out of
          the decisions on totality, had we not gotten there already via
          another path.  Our notion of totality accounts for
          "remainder", which includes things like novel subclasses of
          sealed types that did not exist at compile time, which it
          would not be reasonable to ask users to write code to deal
          with, and null fits into this treatment as well.  We type
          check that a switch is sufficiently total, and then insert
          extra code to catch "silly" values that are not otherwise
          handled, including null, and throw.  (This also enables DA
          analysis to truly trust switch totality.)<br><br>
          Where we land is a single unified switch construct that can be
          either a statement or an expression; that can use either
          old-style flow (colon) or the more constrained flow style
          (arrow); whose case labels can be constant, patterns
          (including guarded patterns), or a mix of the two; which can
          accept the legacy null-hostility behavior, or can override it
          by explicitly using nullable case labels; and which are almost
          always type checked for totality (with some temporary, legacy
          exceptions.)  Fallthough is basically unchanged; you can get
          fallthrough when using the old-style flow, but becomes less
          important as fallthrough is (mostly) nonsensical in the
          presence of pattern cases with bindings, and the compiler
          prevents this misuse.  The distinction between "legacy"
          switches and pattern switches is temporary, with a path to
          getting to "all switches are total" over time.  <br><br>
          I think we've done a remarkable job at rehabilitating this
          monster.  <br><br><br>
          *Someone actually suggested using the syntax "new switch", on
          the basis that new was already a keyword.  Would not have aged
          well.  <br></font></font> </blockquote>
    <br><br></blockquote></div></div></body></html>