[HN Gopher] JEP draft: Exception handling in switch
       ___________________________________________________________________
        
       JEP draft: Exception handling in switch
        
       Author : vips7L
       Score  : 101 points
       Date   : 2024-04-19 16:22 UTC (6 hours ago)
        
 (HTM) web link (openjdk.org)
 (TXT) w3m dump (openjdk.org)
        
       | atomicnumber3 wrote:
       | This will really help using streams and just being
       | expression/HOF-oriented in general, I often find myself writing
       | custom FunctionalInterface's just to capture an exception instead
       | of writing a Consumer/Function/etc. maybe this will help ease
       | that a bit. I still wish there was a way to just "bail out"
       | execution of a lambda like you can a for loop if the function the
       | lambda is used in is declared to throw the exception in question.
       | 
       | But I often find that I'm not thinking functional enough if I
       | find myself trying to do that. But also sometimes I'm forced to
       | by surrounding APIs etc.
        
         | tadfisher wrote:
         | My approach lately is either:
         | 
         | - Ban exception propagation altogether: in Kotlin, wrap the
         | computation in 'runCatching' to get a Result<T>; or in Java,
         | use result4j [1] which provides similar functionality.
         | 
         | - Let exceptions propagate, catching them at as high a level as
         | possible.
         | 
         | Which one to go with depends on the type of program I'm
         | writing. If it's a GUI tool, I go with the first approach,
         | because I want to display errors to the user. If it's a CLI
         | tool or backend service, I go with the second option, because I
         | want to short-circuit the program as soon as possible to avoid
         | potential logic errors.
         | 
         | [1]: https://github.com/sviperll/result4j
        
           | groestl wrote:
           | I tend to do the former only when writing executors, task
           | queues, retry strategies, batch handlers, and the likes,
           | otherwise rely on 2) (the UI is just another "high level")
        
           | cogman10 wrote:
           | I mostly work in the backend and yeah, the second approach is
           | what I value the most.
           | 
           | Particularly because it can provide a ton of useful context
           | (if logging is correctly setup) and doesn't end up littering
           | the code with try/catch blocks and stuttered logging.
        
           | bedobi wrote:
           | using RunCatching is inadvisable, it catches too much and can
           | break lots of things in hard to detect ways
           | 
           | https://github.com/sksamuel/tabby/blob/0fa37638712efd6b059f2.
           | ..
           | 
           | (though I recommend using the Arrow library, it has great
           | types for fixing all the countless foot guns Kotlin devs
           | insist on adding to the language and native libraries)
        
           | ackfoobar wrote:
           | > Let exceptions propagate, catching them at as high a level
           | as possible.
           | 
           | This can't be done cleanly in Java.
           | 
           | Checked exception can encode union types, but this extra
           | power is not complemented anywhere else in Java's type
           | system. E.g. in a `Consumer` lambda passed to `forEach`,
           | Java's checked exception forces you to convert that to a
           | RuntimeException.
        
             | PhilipRoman wrote:
             | I'd say it is due to a lack of variadic type parameters.
             | You can write something like this:                   <T, X
             | extends Throwable> void forEach(ThrowingConsumer<T, X> f)
             | throws X;
             | 
             | But the only way to have multiple exception types without
             | losing static type checking is to have multiple X
             | parameters, like X1, X2, X3... (with unused parameters
             | being set to some subtype of RuntimeException so that they
             | do not participate in checked exception handling).
             | 
             | Whether or not it is worth to write this madness just to
             | satisfy one's OCD is up to the reader.
        
               | ackfoobar wrote:
               | > it is due to a lack of variadic type parameters
               | 
               | To be pedantic, it is due to union types "not
               | complemented anywhere else in Java's type system". Adding
               | variadic type params is _a_ way to solve this. Another
               | way is, of course, to support union types.
               | 
               | > with unused parameters being set to some subtype of
               | RuntimeException
               | 
               | Or the `Nothing` type (`never` in TypeScript), where `A |
               | Nothing = A`.
        
           | wiseowise wrote:
           | > - Ban exception propagation altogether: in Kotlin, wrap the
           | computation in 'runCatching' to get a Result<T>; or in Java,
           | use result4j [1] which provides similar functionality.
           | 
           | 'runCatching' was never intended for user code. It was made
           | for internal use in coroutines machinery and when community
           | complained they made it public. But it is still a half-baked
           | API that is better to avoid (catching CancellationException,
           | doesn't compose well in general).
           | 
           | The whole runCatching/billions of Result<T> copies are worse
           | than Go's "if err !=".
           | 
           | Unless JetBrains rolls out language support for something
           | like Rust's Result, exceptions are superior and Kotlin
           | community would better off embracing them instead of trying
           | to be different for the sake of being different.
           | 
           | https://web.mit.edu/rust-
           | lang_v1.25/arch/amd64_ubuntu1404/sh...
        
         | PaulHoule wrote:
         | Already you can uncheck exceptions by wrapping them in a
         | function like the ones defined here
         | 
         | https://paulhoule.github.io/pidove/apidocs/com/ontology2/pid...
         | 
         | that is
         | something.stream().map(unchecked(SomeClass::methodThatThrows))
        
       | Groxx wrote:
       | > case throws Exception e -> { log(e); yield score(0); }
       | 
       | ... and this is why people so frequently break thread interrupts
       | in Java. most Java sample code (and even official-feeling docs
       | like this) violates basic exception hygiene.
       | 
       | ---
       | 
       | edit: that aside, I can see lots of uses for this pattern, and I
       | like the clear scoping quite a bit. Seems like a good idea on the
       | surface at the very least - I don't have enough experience here
       | to really make a "good idea or no" claim.
        
         | gizmo686 wrote:
         | This complaint is not really about the proposed change, it is a
         | longstanding problem with Java. As long as you treat exceptions
         | with the sane care in switch statements as you do in try
         | statements, this doesn't introduce any new problems.
        
           | bedobi wrote:
           | > it is a longstanding problem with Java
           | 
           | it's, along with null pointers, probably the no 1 biggest
           | foot gun in the language, responsible for countless bugs in
           | pretty much every app ever written in the language, and it's
           | treated as some unaddressed elephant in the room we don't
           | talk about or consider alternatives to
        
             | PaulHoule wrote:
             | It is easy to put the right behavior in a function wrapper
             | like
             | 
             | https://github.com/paulhoule/pidove/blob/97f8ec697d2890f13b
             | a...
        
             | dgellow wrote:
             | C# added nullable reference types a few years ago. Is there
             | an equivalent in Java? That makes dealing with null pretty
             | much a non-issue
             | 
             | https://learn.microsoft.com/en-us/dotnet/csharp/nullable-
             | ref...
        
         | grishka wrote:
         | > and this is why people so frequently break thread interrupts
         | in Java.
         | 
         | By the way. I've been writing predominantly Java over the last
         | 15 years and I absolutely hate it that InterruptedException is
         | checked. It gets in the way All. The. Damn. Time. All for those
         | 0.1% of cases when you need to be able to cancel a blocking
         | operation from another thread.
        
       | agumonkey wrote:
       | Next: handling classes in functions.
        
       | sgammon wrote:
       | I like it. `switch throws` looks like a really clean construct.
        
       | mirekrusin wrote:
       | I don't agree with reasoning to not call it "case catch ...":
       | case catch would be asking "did it evaluate to something that
       | catches this exception?", which doesn't make sense.
       | 
       | Interpretation as "did it evaluate to something that would be
       | catched like this?" makes perfect sense and is way more
       | intuitive. Even "catches" would be better but why introducing new
       | keyword?
       | 
       | "throws" is way too close to an action of throwing.
        
         | extraduder_ire wrote:
         | From my reading, they wanted it to match the sort of grammar
         | that type constructor/type cases have. (looks like a
         | declaration) I have not read the mailing list discussion to see
         | what other alternatives were suggested.
         | 
         | My kneejerk reaction would be to prefer something like "threw"
         | in place of "throws", but I'm sure there's a reason not to.
        
           | lolinder wrote:
           | > I'm sure there's a reason not to
           | 
           | Language designers try to avoid adding new keywords if at all
           | possible, especially ones that are short and common. Doing so
           | requires either breaking thousands of projects and APIs or
           | adding a bunch of complexity to the grammar and syntax
           | highlighting tooling (so threw is only a keyword in some
           | contexts but not others).
        
         | mrighele wrote:
         | You're matching with the result of the expression inside the
         | switch, and in case of an exception the result is something
         | being throw, not something being catched, so the chosen
         | approach seems correct to me.
         | 
         | given the code                   Future<Box> f = ...
         | switch (f.get()) {             case Box(String s) when
         | isGoodString(s) -> score(100);             case Box(String s)
         | -> score(50);             case null
         | -> score(0);             case throws CancellationException ce
         | -> ...ce...             case throws ExecutionException ee
         | -> ...ee...             case throws InterruptedException ie
         | -> ...ie...         };
         | 
         | I read the above as
         | 
         | "if f.get() is a Box(...) then ..."
         | 
         | "if f.get() is null then ..."
         | 
         | "if f.get() throws ExecutionException then ..."
        
           | mirekrusin wrote:
           | catch clauses in try/catch are also matching something being
           | thrown.
           | 
           | People don't have problem reading try/catch, are used to it,
           | it's already there, semantics match - why complicate things?
           | 
           | Case is better read as "captures ... [when ...]" and "case
           | catch" simply means capturing an exception - the same way as
           | try/catch does it.
           | 
           | If you flip it around - if somebody would do an experiment
           | where they'd ask 100 developers to imagine that java supports
           | catching exceptions in switch statements my bet is that
           | almost all, if not all would write it as "case catch ...".
           | 
           | And there is really nothing fundamentally wrong with that.
           | You can't catch catch handler, you can only catch an
           | exception.
        
           | speed_spread wrote:
           | Further bikeshedding this, since the JEP admits regular
           | clauses and Exception clauses should be kept separate as they
           | are treated differently, we could as well retain the original
           | catch syntax:                   switch (f.get()) {
           | case Box(String s) when isGoodString(s) -> score(100);
           | catch InterruptedException ie           -> ...ie...
           | };
           | 
           | Current try/catch gymnastics are laborious, requiring blocks
           | making usage unwieldy in otherwise-one-line lambdas.
           | Requiring "case throws" is yet more useless syntax inflation.
           | It would be nice to keep things streamlined this once.
        
         | grishka wrote:
         | English isn't my native language but I read that as "in case it
         | throws this type of exception" so "case throws" makes perfect
         | sense to me.
        
       | pizlonator wrote:
       | Super cool.
       | 
       | Best part about this language proposal format is how it spells
       | out goals and non-goals so clearly. Having the non-goals, and
       | dialing them in, is really great.
        
       ___________________________________________________________________
       (page generated 2024-04-19 23:01 UTC)