JEP draft: Exception handling in switch
openjdk.org
openjdk.org
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.
- 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.
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.
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)
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.
<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.
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`.
‘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...
Good callout on the stdlib `Result`, a lot of people aren't aware of the `CancellationException` issue. Another pain point is that the error is constrained to `Throwable`, which is rather obtuse for general business logic (and you're likely generating stack traces needlessly unless you disable `writableStackTrace` on your custom throwables).
I'm a fan of https://github.com/michaelbull/kotlin-result which has a fantastic API and supports monad comprehension which helps avoid the "arrowheads" from not having a built in operator.
Please could you elaborate on what "looking thread safe" means to you? The only portion of the library that supports concurrency *is* thread safe - the unit tests[2] prove it and the use of concurrency primitives such as Kotlin's Mutex[3] are indicative of this.
I truly have no idea how you've judged the entirely of the library on whether it's "thread safe" when there is a single function (in an extension library, not the core one) that's related to concurrency and it is very clearly using concurrency primitives as intended.
With regards to "being cautious using it", you don't need to be. The maven central statistics suggest its being downloaded 300,000 times per month. If there was something wrong with it, it's likely somebody would have raised this already given how frequently the project has been adopted over the last seven years.
[1] https://github.com/michaelbull/kotlin-result/blob/master/kot...
[2] https://github.com/michaelbull/kotlin-result/blob/master/kot...
[3] https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-corout...
If this was true, why do Jetbrains themselves keep reinventing their own per-domain Result classes for their new code? For example, in their coroutines library they invented another Result type specifically for Channels[1] and isn't interoperable at all with their other Result type[2] that was also originally written to facilitate some of the coroutines code.
If Jetbrains' own internal library authors keep reaching for a Result type then I think we're better off following suit, because if they thought exceptions were superior in this example, they would have used them.
[1] https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-corout...
[2] https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
https://paulhoule.github.io/pidove/apidocs/com/ontology2/pid...
that is
something.stream().map(unchecked(SomeClass::methodThatThrows)) 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.
My kneejerk reaction would be to prefer something like "threw" in place of "throws", but 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).
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 ..."
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.
We don't have a problem reading try/catch because it comes from an era when Java was more procedural and less functional. "case throws" makes more sense in the functional-style Java era. (Well, makes more sense to me at least)
EDIT: To clarify "catch" is a verb and contains a block of executable statements. "case throws" maps to an expression (because I assume most people writing new code that uses this will be using switch expressions, not switch statements) In this context, "case throws" is a better choice because you are talking about the _expression_ inside the "switch".
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.I would even accept just "catch", such that a switch can hold a mix of "case" and "catch" clauses, which would be most natural.
... 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.
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
https://github.com/paulhoule/pidove/blob/97f8ec697d2890f13ba...
https://learn.microsoft.com/en-us/dotnet/csharp/nullable-ref...
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.
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.