<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>