Most active commenters
  • lmm(4)
  • rixed(3)
  • valcron1000(3)

←back to thread

873 points belter | 12 comments | | HN request time: 0s | source | bottom
Show context
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 #
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 #
1. rixed ◴[] No.42948382[source]
"None" to represent any type of failure sounds similarly fundamentally flawed too.
replies(1): >>42948635 #
2. lmm ◴[] No.42948635[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 #
3. rixed ◴[] No.42951257[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 #
4. valcron1000 ◴[] No.42954801[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 #
5. lmm ◴[] No.42958017{3}[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 #
6. lmm ◴[] No.42958278{3}[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 #
7. rixed ◴[] No.42960838{4}[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?

8. tome ◴[] No.42962712{3}[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 #
9. nsm ◴[] No.42964771{4}[source]
Aren't checked exceptions the same as effect systems in the narrow case of error handling?
10. valcron1000 ◴[] No.42967529{4}[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 #
11. valcron1000 ◴[] No.42967550{4}[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.
12. lmm ◴[] No.42979476{5}[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.