Exception handling in switch (Preview)

Kevin Bourrillion kevinb9n at gmail.com
Thu Apr 25 02:50:22 UTC 2024


Hey Angelos!

For whatever my take is worth here, I'm also skeptical. I think the bar for
adding another way to catch exceptions should be really high, and the
benefits here wouldn't clear it. I just don't expect the nested
switch-in-try will be painful enough *often* enough.

This feature would only be applicable when the whole
possibly-exception-producing code can fit into a single expression in the
switch header, so (a) you'll have to revert to the other form often enough,
and (b) highjinks might ensue as users cram too much code into there...

Normally I expect that when I see an expression, with no curly braces (or
->) involved, my mental model is that that expression gets evaluated and
*then* the resulting value is passed to the surrounding context. This
proposed version of switch seems to work differently than that, but with no
curly braces to set it off, and that feels novel to me. Maybe it's more
precedented than I think though? (for/while still fit this, considering
that execution returns to the point just before the header so the
expressions are naturally evaluated again...)

Now, if there was strong motivation to support actual *patterns* in these
exception cases then that would seem to justify this. Although in that case
I'd ask why regular catch clauses shouldn't accept patterns as well.

~~

If it does proceed then I must take exception (har) with using "throws" to
mean "catch"! Today we have this breakdown

* throws - what might get thrown / is allowed to be thrown
* throw - what IS actually getting thrown
* catch - what HAS actually been thrown

We expect users to learn that, and the current design would ask them to
partly unlearn it. Imho, `case catch FooException` is better!

I hope this is helpful...


On Mon, Apr 22, 2024 at 11:56 PM Brian Goetz <brian.goetz at oracle.com> wrote:

> To the question “does Java need this”, well, of course we don’t *need* it;
> we do have try-catch statements.  But as Dan points out, the main challenge
> of using methods that both return a value and throw exceptions is that we
> cannot handle all the results uniformly.  And as the JEP points out,
> backing off to try-catch is doubly painful: not only do we have to use two
> constructs, but we are back to statement-land, which is far more error
> prone and less composable than expressions.
>
> As to “why don’t we just make try-catch an expression”, well, that’s where
> we started with this feature exploration.  It turns out to just be too weak
> to be useful.  The main constraint is that the try and catch parts have to
> yield the same type, but the constraint to produce a “default” value of
> that type from the catch arms is just too constraining.  If you try some
> nontrivial examples this becomes clear pretty quickly.
>
> But there is one criticism of this feature that is I think at the root of
> what you are getting at, which is that having effect cases in switch
> produces only a shallow-ish unification (or, alternately, that it is not
> the primitive.)  Having effect cases lets us deal uniformly with all the
> consequences of evaluating the selector uniformly in one place, but like
> try-catch, you have to deal with them _right there_.  Whereas with a Try
> monad, you could capture the Try and then process it by passing it to
> another method, putting it on a queue and letting some other thread process
> it, etc.
>
> If we were to excavate to the bottom, then we would likely want `try e` to
> evaluate to a Try monad, at which point the current proposal is a sugary
> representation of:
>
>     switch (try e) {
>         case Success(P1) -> …
>         case Success(P2) -> …
>         case Failure(E1) -> …
>     }
>
> We explored this point as well in the exploration, and backed off.  But we
> can do this in either order; if we have a `try e` primitive that evaluates
> to a Try, then we can retroactively redefine a switch with `case throws`
> clauses to be sugar for the above.
>
> I will think further about the pattern matching connection you propose.
>
> On Apr 20, 2024, at 10:00 AM, Tagir Valeev <amaembo at gmail.com> wrote:
>
> Dear experts,
>
> looking into this proposal, I'm really not convinced that Java needs it.
> We already have try-catch statements, and it sounds strange to provide
> another way to express the same semantics. I don't see what the new
> construct adds, aside from a bit of syntactic sugar. On the other hand, it
> creates a new source of subtle bugs, especially when exceptions are
> unchecked. E.g., consider:
>
> switch(a.b().c().d()) {
> case ...
> case throws RuntimeException ex -> handle(ex);
> }
>
> Now, one may want to refactor the code, extracting a.b(), a.b().c(), or
> the whole a.b().c().d() to a separate variable for clarity, or to avoid a
> long line.
> This action is usually safe, and it was totally safe in switches so far
> (even with patterns and case null). Now, it's not safe, as exceptions
> thrown from the extracted part are not handled by the 'case throws' branch.
> I don't see a good way to perform this refactoring in a semantically
> equivalent way. The only possibility I see is to duplicate the exception
> handler in the external catch:
>
> try {
>   var ab = a.b();
>   switch(ab.c().d()) {
>   case ...
>   case throws RuntimeException ex -> handle(ex);
>   }
> }
> catch(RuntimeException ex) {
>   handle(ex); // duplicated code
> }
>
> As switch selector does not allow using several expressions or to declare
> new variables, extract/inline refactorings can easily become very
> painful, or cause subtle bugs if not performed correctly.
> Note that it's not a problem inside usual try-catch statement (*), as you
> can easily add or remove more statements inside the try-body.
>
> (*) Except resource declaration, but it's rarely a problem, and in some
> cases it's still possible to extract parts as separate resources, because
> you can declare several of them
>
> I think, instead of repurposing switch to be another form of try-catch we
> could add more love to try-catch allowing it to be an expression with
> yields in branches. The proposed JEP allows something like this:
>
> Integer toIntOrNull(String s) {
>   return switch(Integer.parseInt(s)) {
>     case int i -> i;
>     case throws NumberFormatException _ -> null;
>   }
> }
>
> But we are still limited by a single expression in the selector. An
> alternative would be
> Integer toIntOrNull(String s) {
>   return try { yield Integer.parseInt(s); }
>     catch(NumberFormatException _) { yield null; };
> }
> Here, all kinds of refactorings are possible. And we actually don't need
> to express pattern matching, because we essentially don't need any pattern
> matching.
>
> Also, note that some of the situations which are usually solved with
> exception handling in modern Java (e.g. Pattern.compile ->
> PatternSyntaxException, or UUID.fromString -> IllegalArgumentException, or
> Integer.parseInt above) will be covered in future by member patterns. So
> probably if we concentrate more on member patterns, people will need much
> less exception handling in business logic, and such an enhancement will be
> not so useful anyway? Speaking about the sample from the JEP, can we
> imagine something like this in the future (sic!) Java?
>
> switch(future) {
>   case Future.cancelled() -> ...
>   case Future.interrupted() -> ...
>   case Future.failed(Exception ex) -> ... // no need to unwrap
> ExecutionException manually
>   case Future.successful(Box b) -> ...
> }
>
> One more note about the JEP text. It's unclear for me whether 'case throw'
> branches could catch a residual result. More precisely, if MatchException
> happens, or NullPointerException happens (selector evaluated to null, but
> there's no 'case null'), can these exceptions be caught by the 'case
> throws' branches in the same switch?
>
> With best regards,
> Tagir Valeev.
>
>
> On Fri, Apr 19, 2024 at 3:05 PM Angelos Bimpoudis <
> angelos.bimpoudis at oracle.com> wrote:
>
>> Dear spec experts,
>>
>> A while ago we discussed on this list about enhancing the switch​ construct
>> to
>> support case​ labels that match exceptions thrown during evaluation of
>> the
>> selector expression. A draft JEP for this feature is now available at:
>>
>> https://bugs.openjdk.org/browse/JDK-8323658
>>
>> Please take a look at this new JEP and give us your feedback.
>>
>> Thanks,
>> Aggelos
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-spec-experts/attachments/20240424/07ec9ff6/attachment-0001.htm>


More information about the amber-spec-experts mailing list