Most active commenters
  • lmm(8)
  • baq(7)
  • bluGill(6)
  • Terr_(4)
  • catlifeonmars(4)
  • dustingetz(3)
  • The_Colonel(3)
  • rixed(3)
  • taeric(3)
  • valcron1000(3)

←back to thread

873 points belter | 81 comments | | HN request time: 0.597s | source | bottom
1. Terr_ ◴[] No.42946597[source]
> Java is a great language because it's boring [...] Types are assertions we make about the world

This is less of a mind-was-changed case and more just controversial, but... Checked Exceptions were a fundamentally good idea. They just needed some syntactic sugar to help redirect certain developers into less self-destructive ways of procrastinating on proper error handling.

In brief for non-Java folks: Checked Exceptions are a subset of all Exceptions. To throw them, they must be part of the function's type signature. To call that function, the caller code must make some kind of decision about what to do when that Checked Exception arrives. [0] It's basically another return type for the method, married with the conventions and flow-control features of Exceptions.

[0] Ex: Let it bubble up unimpeded, adding it to your own function signature; catch it and wrap it in your own exception with a type more appropriate to the layer of abstraction; catch it and log it; catch it and ignore it... Alas, many caught it and wrapped it in a generic RuntimeException.

replies(13): >>42946899 #>>42946979 #>>42947054 #>>42947147 #>>42947485 #>>42947568 #>>42948130 #>>42948153 #>>42948666 #>>42951688 #>>42952999 #>>42953957 #>>42984777 #
2. dustingetz ◴[] No.42946899[source]
remind me why they don’t work? because “throws Exception” propagates virally to every method in the codebase?
replies(3): >>42947046 #>>42948691 #>>42949347 #
3. pjc50 ◴[] No.42946979[source]
It was botched from the start because there's so many opportunities for unchecked exceptions as well. Without a more sophisticated type system that represented nullability, you can get NullPointerException anywhere. Divide by zero. And so on.

You also have a problem similar to "monads and logging": if you want to log from anywhere in your program, your logging function needs to be exception-tight and deal with all the possible problems such as running out of disk space, otherwise you have to add those everywhere.

replies(3): >>42948468 #>>42950029 #>>42960202 #
4. rad_gruchalski ◴[] No.42947046[source]
Well, if you don’t handle them then… yes? What else can be done?
replies(1): >>42947285 #
5. flir ◴[] No.42947054[source]
> Alas, many caught it and wrapped it in a generic RuntimeException.

Actually sounded great right up until this point. Deal with it, or explicitly acknowledge that you do not. It's honest.

Apparently other developers are why we can't have nice things.

replies(1): >>42947779 #
6. pjmlp ◴[] No.42947147[source]
I agree, although I would like to point out Java usually gets the blame for what was actually an idea being done in CLU, Mesa, Modula-3 and C++, before Oak came to be and turned into Java.

Additionally, the way result types work, isn't much different, from type system theory point of view.

I really miss them in .NET projects, because no one reads method documentation, or bothers to have catch all clauses, and then little fellow crashes in production.

replies(1): >>42958363 #
7. karatinversion ◴[] No.42947285{3}[source]
There's also the fact that common methods threw exception types that were not final, and in fact overly generic. If I call a method that declares itself to throw NoSuchFileException or DirectoryNotEmptyException, I can have a pretty good idea what I might do about it. If it throws IOException without elaboration, on the other hand...
replies(1): >>42947453 #
8. jkrejcha ◴[] No.42947453{4}[source]
With regards to I/O, there are generally any number of weird errors you can run into, file not found, directory exists, host unreachable, permission denied, file corrupt, etc etc. Like anything file related will be able to throw any of those exceptions at almost any point in time.

I think IOException (or maybe FileSystemException) is probably the best you can do in a lot of I/O cases unless you can dedicate time to handling each of those specially (and there's often not a lot much more you can do except for saying "Access is denied" or "File not found" to the user or by logging it somewhere).

replies(1): >>42950220 #
9. girvo ◴[] No.42947485[source]
Checked exceptions as an idea are great (Nim’s usage of something similar is excellent) but yeah Javas particular implementation was annoying and easy to avoid, so most did.
10. baq ◴[] No.42947568[source]
> adding it to your own function signature

This is precisely why they are so bad: checked exceptions must not be allowed to be used outside the package (or jar, or whatever, just limit it) otherwise they cause non-local build failures in all dependencies. They're fine if you are developing the artifact that's going to be deployed.

replies(2): >>42949399 #>>42950468 #
11. mrkeen ◴[] No.42947779[source]
It's already very unlikely that you can 'recover-and-proceed' in the context of any business app exception (Security violation, customer not found, no such payment, etc.).

So what's left in exception handling is logging and/or rethrowing. And the 'nasty hackish way' of doing it (RuntimeException) already passes a complete stack trace up to the caller.

replies(1): >>42948980 #
12. kaapipo ◴[] No.42948130[source]
Nah, I think having a monadic Maybe/Option type and first-class support for it is the correct solution. Exceptions are fundamentally flawed.
replies(1): >>42948382 #
13. The_Colonel ◴[] No.42948153[source]
> They just needed some syntactic sugar to help redirect certain developers into less self-destructive ways of procrastinating on proper error handling.

Syntactic sugar it needs is an easy way (like ! prefix) to turn it to a runtime exception.

Procrastinating on exceptions is usually the correct thing to do in your typical business application - crash the current business transaction, log the error, return error response. Not much else to do.

Instead the applications are now littered with layers of try-catch-rethrow (optionally with redundant logging and wrapping into other useless exceptions) which add no benefit.

replies(2): >>42948496 #>>42949472 #
14. rixed ◴[] No.42948382[source]
"None" to represent any type of failure sounds similarly fundamentally flawed too.
replies(1): >>42948635 #
15. jeroenhd ◴[] No.42948468[source]
Unchecked exceptions are just Java's weird way of what languages call panicking these days. They suck, but as long as you don't throw them yourself and catch them in a logical place (i.e. at request level so your web server doesn't die, at queue level so your data processing management doesn't lock up, etc.) you can usually pretty much ignore them.

The worst part about them is that for some reason even standard library methods will throw them. Like when you try `list.Add(1)` on a list without checking if said list is read-only. The overhead of having to read every single bit of documentation in the standard library just to be ahead of panicking standard methods is infuriating.

That's got nothing to do with the concept of checked/unchecked exceptions, though, that's just Java's mediocre standard library.

replies(1): >>42959998 #
16. jeroenhd ◴[] No.42948496[source]
The try/catch/rethrow model can easily be substituted by just adding a `throws` to the method. If you truly don't care, just make your method `throws Exception` or even `throws Throwable` and let the automatic bubbling take care of making you handle exceptions at top level.
replies(1): >>42948857 #
17. lmm ◴[] No.42948635{3}[source]
That's why you need not just an Either/Result type, but proper monads so that you can use the same functions with both.
replies(2): >>42951257 #>>42954801 #
18. Cthulhu_ ◴[] No.42948666[source]
I mean on paper they're a good idea and well implemented etc. However, the main two flaws with exceptions are that one, most exceptions are not exceptional situations, and two, exceptions are too expensive for dealing with issues that aren't exceptional like that.
19. lmm ◴[] No.42948691[source]
Because you can't capture the evaluation of a function as a value, or write the type of it. E.g. try to write a generic function that takes a list and a callback, and applies the callback to every element of the list. Now what happens if your callback throws a checked exception? It doesn't work and there's no way to make it work, you just have to write another overload of your function and copy/paste your code. Now what happens if your callback throws two checked exceptions? It doesn't work and there's no way to make it work, you just have to write another overload of your function and copy/paste your code. And you'll never guess what happens if your callback throws three checked exceptions!
replies(2): >>42948974 #>>42949045 #
20. The_Colonel ◴[] No.42948857{3}[source]
That (or rather checked exceptions in general) doesn't play well with lambdas / streams.
21. dustingetz ◴[] No.42948974{3}[source]
what is "it doesn't work" ? The exception is part of the type, so it doesn't typecheck unless all callbacks are of type "... throws Exception"? What's the problem with that? It's not generic enough, i.e. the problem is Java generics are too weak to write something like "throws <T extends Exception>"? (Forgive me, it's been 13 years since I wrote java and only briefly, the questions are earnest)

edit, so like `@throws[T <: Exception] def effect[T](): Unit` or something, how is it supposed to work?

replies(1): >>42949296 #
22. wavemode ◴[] No.42948980{3}[source]
"recover and proceed" generally means "log the error and then continue processing on other data, rather than exiting entirely"

Lots of different kinds of software tends to follow this pattern - web servers, data pipelines, compilers/build tools, etc.

23. AnimalMuppet ◴[] No.42949045{3}[source]
Make the signature of your generic callback "throws Throwable". It's generic; it should never care about the specific types that the callback can throw.

(Except that then you have to decide what your generic function is going to do if the callback throws an exception...)

replies(3): >>42949245 #>>42949508 #>>42953221 #
24. lmm ◴[] No.42949245{4}[source]
> Make the signature of your generic callback "throws Throwable". It's generic; it should never care about the specific types that the callback can throw.

> (Except that then you have to decide what your generic function is going to do if the callback throws an exception...)

Exactly. Presumably you don't want to handle them and want to throw them up to the caller. But now your function has to be "throws Throwable" rather than throwing the specific exception types that the callback throws.

25. lmm ◴[] No.42949296{4}[source]
You can't be polymorphic between not throwing and throwing, or between throwing different numbers of exceptions. You have to write something like:

    <R> List<R> map(Function<? super T,? extends R> mapper) { ... }
    <R, E1 extends Throwable> List<R> map(FunctionThrows1<? super T,? extends R, E1> mapper) throws E1 { ... }
    <R, E1 extends Throwable, E2 extends Throwable> List<R> map(FunctionThrows2<? super T,? extends R, E1, E2> mapper) throws E1, E2 { ... }
and so on until you get bored.
replies(2): >>42949471 #>>42951118 #
26. bluGill ◴[] No.42949347[source]
Because eventually someone will want to add another exception to a new leaf class and the proper place to handle it is 20 functions down the call tree and every single one of those 20 functions needs to now add that new exception to their signature even though only the handler function cares and you adjusted it.

I haven't done java in decades, but I imagine this would get really nasty if you are passing a callback to a third party library and now your callback throws the new exception.

Checked exceptions seem like a great idea, but what java did is wrong. I'm not sure if other implementations are better.

replies(1): >>42949801 #
27. catlifeonmars ◴[] No.42949399[source]
I think that’s more of a problem of changing the function signature. Not specific to checked exceptions.
replies(1): >>42949639 #
28. catlifeonmars ◴[] No.42949471{5}[source]
Ah because there is no way to express E1 | E2 as a type parameter?
replies(1): >>42957772 #
29. pinoy420 ◴[] No.42949472[source]
How many errors are actually recoverable. I bet most thrown exceptions could be replaced with a printf(“it went wrong here”) for all their utility.
replies(2): >>42949677 #>>42949926 #
30. bluGill ◴[] No.42949508{4}[source]
By doing that you lose all the benefits of checked exceptions. If you have checked exceptions everywhere the compiler will tell you when an exception is not handled and in turn you can ensure you handle it.

Of course in general the manual effort to do that in a large code base ends up too hard and so in the real world nobody does that. Still the ideal is good, just the implementation is flawed.

31. baq ◴[] No.42949639{3}[source]
What's specific in checked exceptions is that if you don't handle or silently ignore the new exception, you must change the signature. Then your callers must do the same thing. Then their callers etc. sometimes right down to your public static void main.
replies(2): >>42949717 #>>42952933 #
32. The_Colonel ◴[] No.42949677{3}[source]
Well, usually you want to handle it at some level - e.g. a common REST exception handler returning a standard 500 response with some details about what went wrong. Or retry the process (sometimes the errors may be intermittent)
33. catlifeonmars ◴[] No.42949717{4}[source]
Yep, don’t change the function signature of depended on code. That’s the problem :)

It would be equally problematic to change the method name, or the argument arity or types (ignoring overloading for the moment).

It would be even more problematic if I kept the same function signature, but changed the meaning of the parameters. Then your breakage is silent.

replies(1): >>42950181 #
34. zaphar ◴[] No.42949801{3}[source]
The counter point here is that at least with Checked Exceptions you know only those 20 functions are part of the code path that can throw that exception. In the runtime exception case you are unaware about that 21'st function elsewhere that now throws it and it's not in the correct handling path anymore.

You have no way to assert that the error is always correctly handled in the codebase. You are basically crossing your fingers and hoping that over the life of the codebase you don't break the invariant.

What was missing was a good way to convert checked exceptions from code you don't own into your own error domain. So instead java devs just avoided them because it was more work to do the proper error domain modeling.

replies(1): >>42950023 #
35. bluGill ◴[] No.42949926{3}[source]
I disagree. The real value of exceptions is you can skip 6 levels of functions that have lines like

status = DoThing(); if(status != allIsWell) {return status;}

C++ embedded for a long time has said don't use exceptions they are slow. However recent thinking has changed - turns out in trivial code exceptions are slow but in more real world code exceptions are faster than all those layers if checks - and better yet you won't give up on writing all the if checks. Thus embedded projects are starting turn exceptions on (often optimized exceptions with static pre allocated buffers)

The final "print something when wrong" is of little value, but the unwinding is very valuable.

replies(2): >>42951252 #>>42955003 #
36. bluGill ◴[] No.42950023{4}[source]
Like I said, the implementation is wrong. Adding an exception to that 21st function, and then that whole call chain as well ends up being a lot of work. Sure you eventually find the place to handle it, but it was a lot of effort in the mean time.

It gets worse. Sometimes we can prove that the 21st function because of the way it is calling your function can never trigger that exception, but still it will need code to handle it. If that handler code if the 21st function changes to trigger the exception now should be propagated back down but since you handled the exception before checked exceptions won't tell you that you handled it in the wrong place.

I don't know how to implement checked exceptions right. On paper they have a lot of great arguments for them. However in practice they don't work well in large projects (at least for java)

replies(1): >>42952355 #
37. corytheboyd ◴[] No.42950029[source]
> Without a more sophisticated type system that represented nullability, you can get NullPointerException anywhere.

I started working in Java a few months ago and holy shit does this stick out like a sore thumb. Null checks cascade down from gods domain all the way to hell. But oop we missed one here and caused an outage lol add one more! So much wasted human effort around NPE, and yet, we sit around in a weekly meeting getting yelled at about how stability needs to be taken more seriously. Hrm.

replies(1): >>42950584 #
38. baq ◴[] No.42950181{5}[source]
yes exactly, hence unchecked exceptions are the sane option.
replies(1): >>42951034 #
39. bluGill ◴[] No.42950220{5}[source]
There are fatal IO errors and non-fatal. Most of us don't care about the non-fatal case, but mainframes have the concept of "file isn't available now, load tape 12345", "file is being restored from backup, try again tomorrow" - things that your code could handle (if nothing else you should inform the user that this will take a while). There is also the "read off the end of the file" exception which in some languages is the idiomatic way to tell when you have read the whole file.

But most IO errors are fatal. It doesn't matter if the filename is not found, or the controller had too many errors talking to the drive and gave up - either way your code can do nothing.

40. ◴[] No.42950468[source]
41. pjc50 ◴[] No.42950584{3}[source]
C# half-fixes this with its nullable annotations. I say half-fixes, because the boundary between code that supports them and code that does not is leaky, so you can make a mistake that leaks a null into a non-nullable variable.

If you build an entire program with nullability checking on it's pretty great, though.

replies(2): >>42952074 #>>42954922 #
42. catlifeonmars ◴[] No.42951034{6}[source]
Doesn’t that just introduce a silent breaking change to your downstream consumers? That sounds worse.

It sounds ok from a library authors perspective but definitely not from a library consumer perspective.

A sane thing to do would be to do a version bump.

replies(1): >>42952002 #
43. dustingetz ◴[] No.42951118{5}[source]
ah!
44. hasley ◴[] No.42951252{4}[source]
Do you have any references for that? I used to avoid exceptions on small Cortex M0/M3 devices as well.
replies(1): >>42952302 #
45. rixed ◴[] No.42951257{4}[source]
Yes, but then you have to handle every possible errors at every call point, and wrap all those you can't handle at the calling site into your own return type... This is well documented.

One should be cautious every time it feels like there is an obviously right way to do something that everybody fails to see :)

replies(1): >>42958017 #
46. taeric ◴[] No.42951688[source]
The problem with checked exceptions is they are best explained and utilized in a single threaded execution model where the exceptions can be bubbled up to the operator.

This is not, of course, the only way that checked exceptions can be utilized. But, all too often, that is by far the easiest way to reason on them. They represent a decision that needs to be bubbled up to the operator of the overall system.

Worse, the easy way to explain how the system should resume, is to go back to where the exception happened and to restart with some change in place. Disk was full, continue, but write to a new location. That is, having the entire stack unrolled means you wind up wanting the entire process reentrant. But that is an actively hostile way to work for most workflows. Imagine if, on finding a road was closed, a cab driver took you back to pick up location to ask what you want to do about it.

If it is not something that you want to unwind the stack, or bubble up to the users, then you go through effort to wrap it so that it is another value that is being processed.

replies(1): >>42952429 #
47. baq ◴[] No.42952002{7}[source]
consider the standard library case - e.g. there's a new kind of exception because new storage or network technology demands it. you can't add it without breaking the build of everything everywhere effectively freezing the standard library version for people who don't have the means to fix their build. that's super duper bad.
replies(1): >>42953175 #
48. corytheboyd ◴[] No.42952074{4}[source]
Java, or at least Lombok, seems to have a @NonNull annotation that does what I want— cause code not to build that fails the check, and forces propagation of the annotation.

Reality does indeed feel exactly like what you mentioned with C#, though. The annotation is going to be missing where it’s needed most unless something forces the whole project to use it.

replies(1): >>42952380 #
49. bluGill ◴[] No.42952302{5}[source]
Khalil Estell has some great work on that. https://www.youtube.com/watch?v=bY2FlayomlE is one link - very low level technical of what is really happening. He has other talks and papers if you search his name.
replies(1): >>42953984 #
50. zaphar ◴[] No.42952355{5}[source]
The Rust Result type with accompanying `?` operator and the `Try*` traits are how you implement exceptions correctly. It makes it easy to model the error domain once in your trait implementations and then the `?` does the rest of the work.

You could imagine something similar with Exceptions where there is simple and ergonomic way to rethrow the exception into your own error domain with little to no extra work on the part of the developer.

51. mands ◴[] No.42952380{5}[source]
check JSpecify (https://jspecify.dev) - it's the standardised null annotation package for Java. Intellij understands the annotations so you generally get decent null-checking across your codebase.

Even better, apply at the package level via `package-info.java` (unfortunately sub-packages need to be individually marked as well)

  @NullMarked
  package com.foo;

  import org.jspecify.annotations.NullMarked;
52. kflgkans ◴[] No.42952429[source]
The most common pattern in languages with explicit error handling, is to simply return the error (possibly with some context added) in every function up to the point where the process was started (e.g. an HTTP endpoint handler, or the CLI's main function) to deal with it.

I'm not saying exceptions are good, but I am saying that they do represent the most common error handling pattern.

replies(1): >>42952539 #
53. taeric ◴[] No.42952539{3}[source]
Right, this is largely the same idea. For things that have to be bubbled up, you wind up in the simplistic "single thread of execution by an operator" pattern. And, in that scenario, exceptions work exactly the same as just returning it all the way up. It is literally just making it easier to unwind the stack.

My assertion is that actual error handling in workflows doesn't work in that manner. Automated workflows have to either be able to work with the value where it was broken, or generally just mark the entire workflow as busted. In that scenario, you don't bubble up the exception, you instead bubble up an error code stating why it failed so that that can be recorded for later consideration. Along the way of bubbling up, you may take alternative actions.

replies(1): >>42954958 #
54. Terr_ ◴[] No.42952933{4}[source]
And that is extremely good compared to the same function-writer adding or changing their non-checked exception, for which an 1+ levels removed consumer gets no warning at all until the pager goes off because the system broke in production.
replies(1): >>42953062 #
55. mightyham ◴[] No.42952999[source]
I'm not a fan of checked exceptions because they force you to use a verbose control structure (try-catch) that distracts from the actual logic being expressed. Typically, checked exceptions are also not exceptional, so I prefer working with monadic exceptions, like Rust's Option/Result, because they encourage error recovery code to use normal control flow, keeping it consistent with the rest of the application.

I also find that generally exceptions can't be meaningfully caught until much higher in the call-stack. In which case, a lot of intermediary methods need to be annotated with a checked exception even though it's not something that matters to that particular method. For this reason, I've really come around on the Erlang way of doing things: throwing runtime exceptions in truly exceptional situations and designing top level code so that processes can simply fail then restart if necessary.

56. baq ◴[] No.42953062{5}[source]
Unless the library upgrade is also fixing a 9.9 CVE.
replies(1): >>42953429 #
57. Terr_ ◴[] No.42953175{8}[source]
1. The standard library is special in many ways, particularly because it often isn't shipped along with your product and you can't always control what version is used. Just because something is problematic for those libraries doesn't mean it it's a bad idea everywhere else.

2. The difference between altering your un/checked exceptions is not whether consumers will have to react, but how it shows up and how badly you will ruin their day. A checked exception is unambiguously better. It will immediately break their build at the same time they ought to be expecting build-breaks, and the compiler will give them a clear and comprehensive list of cases to address. In contrast, an unchecked exception may let them compile but it will break their business in production, unpredictably.

replies(1): >>42953518 #
58. throwaway-9111 ◴[] No.42953221{4}[source]
In that case you can use the lombok @SneakyThrows annotation that converts a checked exception to a runtime exception
59. Terr_ ◴[] No.42953429{6}[source]
That same scenario (an emergency version-change to a direct dependency) could also remove a function that your code calls! Yet that does not mean mean compiler-checks are bad, or that the solution is to make a system that lets you yeet it into production anyway.

Look, I get it: Sometimes a Checked Exception defined in a niche spot "infects" higher-level code which adds it to their signatures, because nobody takes the time to convert it into something more layer-appropriate.

But that is the exact same kind of problem you'd also get when library's NicheCalculationResult class is trickling upwards without conversion too! However nobody freaks out over that one. Not because it's mechanically different, but because it's familiar.

replies(1): >>42953897 #
60. baq ◴[] No.42953518{9}[source]
Re 1) when the standard library becomes a major blocker for the runtime version upgrade many people are seriously angry, or depressed.

Re 2) that's what it must've sounded like in theory in the conference room when they were designing that part of the language. In practice, the upgrade of the library will never happen if it breaks the build. Production should catch all runtime exceptions and be able to restart itself gracefully anyway because cosmic rays don't make sense as checked exceptions.

61. baq ◴[] No.42953897{7}[source]
> That same scenario (an emergency version-change to a direct dependency) could also remove a function that your code calls!

absolutely, but the catch is it doesn't affect me transitively. the immediate caller must deal with the issue somehow. with exceptions, it is expected to not handle the ones you have no business handling, so you should change your signature. this propagates upwards and there is no layer of abstraction that can handle this problem without breaking the world. the only somewhat sane way is wrapping the new exception is something that you already handle - if that makes logical sense, which it very well might not.

> NicheCalculationResult class is trickling upwards

yes, and yes people do freak out, not sure why you think they aren't :)

62. ben7799 ◴[] No.42953957[source]
I've pretty much been stuck on java since 1995, I started right around 1.0. There have been some stints of Python or we have some glue code in C/C++ or whatever but we're talking 95% java.

Some of these things are mildly annoying when you think about them in theoretical terms.

But all the professional teams I've been on a (a lot) have successfully dealt with exceptions without issue. The teams settled on an exception and error handling process early in the design phase and it rarely has caused a major issue.

Yet it seems out on the internet in any place where programming languages are discussed it is an insurmountable problem that has caused all projects in Java to fail and the language died an early and unpopular death. It seems if it caused anyone huge problems it was not Java's issue but that team's issue.

There are/were other much bigger issues over the years. Memory leaks have been issues. Spring was hard for many people to deal with back around 2005 or so. The first iterations were quite bad with XML. XML in general caused a lot of issues. J2EE caused a lot of issues just because it was so badly designed early on. (Some of this was because it was birthed out of CORBA, which was itself pretty horrible.) Plenty of issues were caused by using Collections with mixed objects in them early on before Generics were introduced. Visual J++ caused havoc. Different models of web application caused a lot of havoc before we got to Javascript UIs in the browser driven by Web API back ends. JPA was a big mistake IMO. But exceptions were never really a huge problem anywhere.

So many have blamed a problem on Java when it was actually a problem with a library, component, or framework written in Java that became way more popular than it should have been. And along the way there were a lot of "developer influencer celebrities" who were listened too far far more than they should have been. Many of these guys (I can't remember one ever being a woman) sold everyone on ultra complex designs and ways of doing things and the community almost always bought in to a ridiculous degree.

63. hasley ◴[] No.42953984{6}[source]
Nice, thank you!
64. valcron1000 ◴[] No.42954801{4}[source]
As someone who worked with Haskell and Rust in production this is a nightmare: - All functions end up returnig `Either/Result` - Stack traces are gone - Exceptions and panics can still creep so it's not even "safer" - There is no composability of different result types, you need something like `Either<FooError | BarError | ..., A> which is not supported in most languages (I think Scala 3 and Ocaml have this feature), or you create a "general wrapper" like `SomeException` (Haskell) or use `anyhow` (Rust).

Today I write C# at my job and I could not be happier with the usage of exceptions.

replies(2): >>42958278 #>>42962712 #
65. YoshiRulz ◴[] No.42954922{4}[source]
Kotlin fixes the null handling problem too, and with the added benefit of being able to gradually migrate Java code.
66. kflgkans ◴[] No.42954958{4}[source]
Thanks for the additional clarification!
replies(1): >>42955367 #
67. ackfoobar ◴[] No.42955003{4}[source]
> The real value of exceptions is you can skip 6 levels of functions that have lines like

For some reason some Go programmers think those lines are the best thing since sliced bread.

68. taeric ◴[] No.42955367{5}[source]
Certainly! And please let me know if I'm off or otherwise confused here. I can guarantee I'm not the brightest bulb around here! :D
69. lmm ◴[] No.42957772{6}[source]
Yeah. Ironically the JLS includes a complete specification of what the type E1|E2 is, because if you write a catch block that catches both then that's the type of what you catch, there's just no syntax for it.
70. lmm ◴[] No.42958017{5}[source]
> Yes, but then you have to handle every possible errors at every call point, and wrap all those you can't handle at the calling site into your own return type... This is well documented.

What? No you don't. You can just propagate up the errors you can't handle with the types they come with.

replies(1): >>42960838 #
71. lmm ◴[] No.42958278{5}[source]
Stack traces are valuable and important, agreed. (In Scala it's pretty normal to use Either/Result with an exception type on the left so that you still get the stack trace). And it's definitely possible to carry an error state around too far when it's not recoverable and you should have just errored out earlier - ultimately you still need good coding judgement. "All functions end up returnig `Either/Result`" is a sign you're doing something wrong.

> Exceptions and panics can still creep so it's not even "safer"

This part is just as true for checked exceptions - you still have unchecked exceptions too. Ultimately you can't get away from needing a way to bail out from unrecoverable states. But "secondary state that is recoverable and understandable, but should be handled off the primary codepath" is a very useful tool to have in your vocabulary.

> Today I write C# at my job and I could not be happier with the usage of exceptions.

How do you do e.g. input validation? Things that are going to be 4xx errors not 5xx errors, in HTTP terms. Do you use exceptions for those? (I note that C# doesn't have checked exceptions at all, so there's no direct equivalent)

replies(1): >>42967529 #
72. specialist ◴[] No.42958363[source]
Anti checked exception sentiment is the result of cognitive dissonance from the use of flow-of-control obfuscation frameworks.

IoC, DI, aspects, annotations, ORMs, whatever Spring is, runtime code generation, etc.

The rationale is something like "runtime metaprogramming would be really terrific, were it not for all those pesky checked exceptions".

73. ◴[] No.42959998{3}[source]
74. kelnos ◴[] No.42960202[source]
The problem there was really that Java confused unrecoverable errors with recoverable errors. NPEs and divide by zero should make the program abort (possibly with another, completely different mechanism to catch these if you really want to, a la Rust's panic handlers).

Recoverable errors should all be checked exceptions, and a part of each function's type signature. This would still be a huge pain to deal with, though, with the existing syntax.

75. rixed ◴[] No.42960838{6}[source]
What language are you using that will automatically infer this at compilation time?(*)

If I understand properly what you are suggesting, to make it work with my goto language (OCaml), I believe that I would have to make every function return a polymorphic variant for the error (aka technicaly equivalent to mandatory try blocks and boxed return types). The only time I had to deal with a library doing that it was not pleasant but it was too long ago for me to remember the details, probably related to complex compilation error messages.

(*) looking at your github, I guess Scala?

76. tome ◴[] No.42962712{5}[source]
Effect systems such as Bluefin (my own) and effectful allow you to have multiple possible exception effects in scope without having to cram them all into one type (with some sort of "variant" or "open sum" type). It's a very pleasant way to work!
replies(2): >>42964771 #>>42967550 #
77. nsm ◴[] No.42964771{6}[source]
Aren't checked exceptions the same as effect systems in the narrow case of error handling?
78. valcron1000 ◴[] No.42967529{6}[source]
> This part is just as true for checked exceptions - you still have unchecked exceptions too

That's why I'm against checked exceptions in general: I avoid them in Java as much as possible.

> "All functions end up returnig `Either/Result`" is a sign you're doing something wrong.

I agree, that's why I'm sharing that it's a bad idea. I've had this experience recently in a team of very experienced developers (some of them who even have books written). In the wild you have Go in which essentially all non-trivial functions return `X, error`.

> How do you do e.g. input validation? Things that are going to be 4xx errors not 5xx errors, in HTTP terms

Recently I've been working on a piece of code that deals exactly with that. For such use cases we use the `bool Try(out var X)` pattern which follows what you have in the standard library (see `int.TryParse(string input, out int result)`) which works but I'm not a fan of. For this use case a `Either/Result` would work great, but is a very localized piece of code, not something that you would expect to see everywhere. Also, you might want to roll (or use) a specialized `Validation` type that does not short-circuit when possible (ex. in a form you want to check as many fields as possible in a single pass).

In summary, I'm not against the existence of `Either/Result` in general, I think there are great use cases for them (like validation); what I'm against is the usage of them to signal all possible type of errors, in particular when IO is involved.

replies(1): >>42979476 #
79. valcron1000 ◴[] No.42967550{6}[source]
I haven't had the time to play with Bluefin (I'll get to it eventually!) but I did try out effectful. In the later I've only used the `Fail` effect since most of the time I just want to bail out when some invariant is broken. In my experience these fine-grained errors don't provide much value in practice but I understand the appeal for some.
80. lmm ◴[] No.42979476{7}[source]
> In summary, I'm not against the existence of `Either/Result` in general, I think there are great use cases for them (like validation); what I'm against is the usage of them to signal all possible type of errors, in particular when IO is involved.

I agree that a checked error type is a bad way to represent IO errors that most programs won't want to handle (or will handle in a very basic "retry the whole thing at high level" way). I think a lot of functional IO runtimes are converging on a design where IO actions implicitly carry some possibility of error that you don't have to nest in a separate either/result type.

81. d-man ◴[] No.42984777[source]
I think your exception model needs to match your problem domain and your solution.

I work on an Inversion of Control system integration framework on top of a herd of business logic passing messages between systems. If I were to do all over again, then I’d have the business logic:

* return success or failure (invalid input)

* throw exception with expectation that it might work in the near future (timeout), with advice on how long to wait to retry, and how many retries before escalating

* throw exception with expectation that a person needs to check things out (authentication failure)

Unless the business logic catches it, unchecked exceptions are a failure. Discussion about what is what kind of exception is hard, but the business owners usually have strong opinions taking me off the hook.