<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>From: </b>"Brian Goetz" <brian.goetz@oracle.com><br><b>To: </b>"amber-spec-experts" <amber-spec-experts@openjdk.java.net><br><b>Sent: </b>Tuesday, December 12, 2023 10:23:09 PM<br><b>Subject: </b>Effect cases in switch<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="4" face="monospace">Based on some inspiration from
OCaml, and given that the significant upgrades to switch so far
position it to do a lot more than it could before, we've been
exploring a further refinement of switch to incorporate failure
handling as well. <br><br>
(I realize that this may elicit strong reactions from some, but
please give it some careful thought before giving voice to those
reactions.)<br><br></font></blockquote><div><br></div><div>Hello,<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="4" face="monospace"><br></font># Uniform handling of failure in switch<br>
<br>
## Summary<br>
<br>
Enhance the `switch` construct to support `case` labels that match
exceptions<br>
thrown during evaluation of the selector expression, providing
uniform handling<br>
of normal and exceptional results.<br>
<br>
## Background<br>
<br>
The purpose of the `switch` construct is to choose a single course
of action<br>
based on evaluating a single expression (the "selector"). The
`switch`<br>
construct is not strictly needed in the language; everything that
`switch` does<br>
can be done by `if-else`. But the language includes `switch`
because it<br>
embodies useful constraints which both streamline the code and
enable more<br>
comprehensive error checking.<br>
<br>
The original version of `switch` was very limited: the selector
expression was<br>
limited to a small number of primitive types, the `case` labels were
limited to<br>
numeric literals, and the body of a switch was limited to operating
by<br>
side-effects (statements only, no expressions.) Because of these
limitations,<br>
the use of `switch` was usually limited to low-level code such as
parsers and<br>
state machines. In Java 5 and 7, `switch` received minor upgrades
to support<br>
primitive wrapper types, enums, and strings as selectors, but its
role as "pick<br>
from one of these constants" did not change significantly. <br>
<br>
Recently, `switch` has gotten more significant upgrades, to the
point where it<br>
can take on a much bigger role in day-to-day program logic. Switch
can now be<br>
used as an expression in addition to a statement, enabling greater
composition<br>
and more streamlined code. The selector expression can now be any
type. The<br>
`case` labels in a switch block can be rich patterns, not just
constants, and<br>
have arbitrary predicates as guards. We get much richer type
checking for<br>
exhaustiveness when switching over selectors involving sealed
types. Taken<br>
together, this means much more program logic can be expressed
concisely and<br>
reliably using `switch` than previously.<br>
<br>
### Bringing nulls into `switch` <br>
<br>
Historically, the `switch` construct was null-hostile; if the
selector evaluated<br>
to `null`, the `switch` immediately completed abruptly with<br>
`NullPointerException`. This made a certain amount of sense when
the only<br>
reference types that could be used in switch were primitive wrappers
and enums,<br>
for which nulls were almost always indicative of an error, but as
`switch`<br>
became more powerful, this was increasingly a mismatch for what we
wanted to do<br>
with `switch`. Developers were forced to work around this, but the
workarounds<br>
had undesirable consequences (such as forcing the use of statement
switches<br>
instead of expression switches.) Previously, to handle null, one
would have to<br>
separately evaluate the selector and compare it to `null` using
`if`:<br>
<br>
```<br>
SomeType selector = computeSelector();<br>
SomeOtherType result;<br>
if (selector == null) { <br>
result = handleNull();<br>
}<br>
else { <br>
switch (selector) { <br>
case X: <br>
result = handleX();<br>
break;<br>
case Y: <br>
result = handleY();<br>
break;<br>
}<br>
}<br>
```<br>
<br>
Not only is this more cumbersome and less concise, but it goes
against the main<br>
job of `switch`, which is streamline "pick one path based on a
selector<br>
expression" decisions. Outcomes are not handled uniformly, they are
not handled<br>
in one place, and the inability to express all of this as an
expression limits<br>
composition with other language features.<br>
<br>
In Java 21, it became possible to treat `null` as just another
possible value of<br>
the selector in a `case` clause (and even combine `null` handling
with<br>
`default`), so that the above mess could reduce to<br>
<br>
```<br>
SomeOtherType result = switch (computeSelector()) {<br>
case null -> handleNull();<br>
case X -> handleX();<br>
case Y -> handleY();<br>
}<br>
```<br>
<br>
This is simpler to read, less error-prone, and interacts better with
the rest of<br>
the language. Treating nulls uniformly as just another value, as
opposed to<br>
treating it as an out-of-band condition, made `switch` more useful
and made Java<br>
code simpler and better. (For compatibility, a `switch` that has no
`case null`<br>
still throws `NullPointerException` when confronted with a null
selector; we opt<br>
into the new behavior with `case null`.)<br>
<br>
### Other switch tricks<br>
<br>
The accumulation of new abilities for `switch` means that it can be
used in more<br>
situations than we might initially realize. One such use is
replacing the<br>
ternary conditional expression with boolean switch expressions; now
that<br>
`switch` can support boolean selectors, we can replace<br>
<br>
expr ? A : B<br>
<br>
with the switch expression<br>
<br>
```<br>
switch (expr) { <br>
case true -> A;<br>
case false -> B;<br>
}<br>
```<br>
<br>
This might not immediately seem preferable, since the ternary
expression is more<br>
concise, but the `switch` is surely more clear. And, if we nest
ternaries in<br>
the arms of other ternaries (possibly deeply), this can quickly
become<br>
unreadable, whereas the corresponding nested switch remains readable
even if<br>
nested to several levels. We don't expect people to go out and
change all their<br>
ternaries to switches overnight, but we do expect that people will
increasingly<br>
find uses where a boolean switch is preferable to a ternary. (If
the language<br>
had boolean switch expressions from day 1, we might well not have
had ternary<br>
expressions at all.)<br>
<br>
Another less-obvious example is using guards to do the selection,
within the<br>
bounds of the "pick one path" that `switch` is designed for. For
example, we<br>
can write the classic "FizzBuzz" exercise as:<br>
<br>
```<br>
String result = switch (getNumber()) { <br>
case int i when i % 15 == 0 -> "FizzBuzz";<br>
case int i when i % 5 == 0 -> "Fizz";<br>
case int i when i % 3 == 0 -> "Buzz";<br>
case int i -> Integer.toString(i);<br>
}<br>
```<br>
<br>
A more controversial use of the new-and-improved switch is as a
replacement for<br>
block expressions. Sometimes we want to use an expression (such as
when passing<br>
a parameter to a method), but the value can only be constructed
using<br>
statements: <br>
<br>
```<br>
String[] choices = new String[2];<br>
choices[0] = f(0);<br>
choices[1] = f(1);<br>
m(choices);<br>
```<br>
<br>
While it is somewhat "off label", we can replace this with a switch
expression:<br>
<br>
```<br>
m(switch (0) { <br>
default -> { <br>
String[] choices = new String[2];<br>
choices[0] = f(0);<br>
choices[1] = f(1);<br>
yield choices;<br>
}<br>
})<br>
```<br>
<br>
While these were not the primary use cases we had in mind when
upgrading<br>
`switch`, it illustrates how the combination of improvements to
`switch` have<br>
made it a sort of "swiss army knife".<br>
<br>
## Handling failure uniformly<br>
<br>
Previously, null selector values were treated as out-of-band events,
requiring<br>
that users handle null selectors in a non-uniform way. The
improvements to<br>
`switch` in Java 21 enable null to be handled uniformly as a
selector value, as<br>
just another value.<br>
<br>
A similar source of out-of-band events in `switch` is exceptions; if
evaluating<br>
the selector throws an exception, the switch immediately completes
with that<br>
exception. This is an entirely justifiable design choice, but it
forces users<br>
to handle exceptions using a separate mechanism, often a cumbersome
one, just as<br>
we did with null selectors:<br>
<br>
```<br>
Number parseNumber(String s) throws NumberFormatException() { ... }<br>
<br>
try { <br>
switch (parseNumber(input)) { <br>
case Integer i -> handleInt(i);<br>
case Float f -> handleFloat(f);<br>
...<br>
}<br>
}<br>
catch (NumberFormatException e) {<br>
... handle exception ...<br>
}<br>
```<br>
<br>
This is already unfortunate, as switch is designed to handle "choose
one path<br>
based on evaluating the selector", and "parse error" is one of the
possible<br>
consequences of evaluating the selector. It would be nice to be
able to handle<br>
error cases uniformly with success cases, as we did with null.
Worse, this code<br>
doesn't even mean what we want: the `catch` block catches not only
exceptions<br>
thrown by evaluating the selector, but also by the body of the
switch. To say<br>
what we mean, we need the even more unfortunate<br>
<br>
```<br>
var answer = null;<br>
try { <br>
answer = parseNumber(input);<br>
}<br>
catch (NumberFormatException e) {<br>
... handle exception ...<br>
}<br>
<br>
if (answer != null) { <br>
switch (answer) { <br>
case Integer i -> handleInt(i);<br>
case Float f -> handleFloat(f);<br>
...<br>
}<br>
}<br>
```<br>
<br>
Just as it was an improvement to handle `null` uniformly as just
another<br>
potential value of the selector expression, we can get a similar
improvement by<br>
handling normal and exceptional completion uniformly as well.
Normal and<br>
exceptional completion are mutually exclusive, and the handling of
exceptions in<br>
`try-catch` already has a great deal in common with handling normal
values in<br>
`switch` statements (a catch clause is effectively matching to a
type pattern.)<br>
For activities with anticipated failure modes, handling successful
completion<br>
via one mechanism and failed completion through another makes code
harder to<br>
read and maintain. <br>
<br>
## Proposal<br>
<br>
We can extend `switch` to handle exceptions more uniformly in a
similar was as<br>
we extended it to handle nulls by introducing `throws` cases, which
match when<br>
evaluating the selector expression completes abruptly with a
compatible<br>
exception: <br>
<br>
```<br>
String allTheLines = switch (Files.readAllLines(path)) {<br>
case List<String> lines ->
lines.stream().collect(Collectors.joining("\n"));<br>
case throws IOException e -> "";<br>
}<br>
```<br>
<br>
This captures the programmer's intent much more clearly, because the
expected<br>
success case and the expected failure case are handled uniformly and
in the same<br>
place, and their results can flow into the result of the switch
expression.<br>
<br>
The grammar of `case` labels is extended to include a new form,
`case throws`,<br>
which is followed by a type pattern:<br>
<br>
case throws IOException e: <br>
<br>
Exception cases can be used in all forms of `switch`: expression and
statement<br>
switches, switches that use traditional (colon) or
single-consequence (arrow)<br>
case labels. Exception cases can have guards like any other pattern
case. </blockquote><div><br></div><div>I think I would prefer "case throws" to be spell "catch" even if we have to have a discussion about catch(Throwable t) vs catch Throwable t.<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;"><br>
<br>
Exception cases have the obvious dominance order with other
exception cases (the<br>
same one used to validate order of `catch` clauses in `try-catch`),
and do not<br>
participate in dominance ordering with non-exceptional cases. It is
a<br>
compile-time error if an exception case specifies an exception type
that cannot<br>
be thrown by the selector expression, or a type that does not extend<br>
`Throwable`. For clarity, exception cases should probably come
after all other<br>
non-exceptional cases. <br>
<br>
When evaluating a `switch` statement or expression, the selector
expression is<br>
evaluated. If evaluation of the selector expression throws an
exception, and<br>
one of the exception cases in the `switch` matches the exception,
then control<br>
is transferred to the first exception case matching the exception.
If no<br>
exception case matches the exception, then the switch completes
abruptly with<br>
that same exception. </blockquote><div><br data-mce-bogus="1"></div><div>I don't want to be the guy implementing this :)</div><div>Your proposal is fighting against the physics of the VM, when you enter in an exception handler (a catch block), the whole stack disapear so you need some compiler magic to store everything which is on stack into locals before calling a switch expression so you can restore it inside the exception handler. I've implemented a very similar kind of transformation 10 years ago, it severly bloats the produced bytecode. Or maybe you are suggesting that this new switch will use a new bytecode construct (inline exception handler ?), it's more or less what JSR/RET was, but perhaps there is a better way.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>I understand the appeal of such construct, it's a way to switch (eheh) from a world with exceptions to a more functional world where errors are values.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>regards,<br data-mce-bogus="1"></div><div>RĂ©mi<br data-mce-bogus="1"></div><div><br></div></div></div></body></html>