Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Option and Result types, as implemented today in mainstream languages (ie. mostly anemically), are not the answer to exceptions being a mess.

Exceptions have a lot of additional functionality in larger ecosystems such as:

- Backtraces ie. showing the exact path of the error from its source to whereever it was handled, in a zero-cost way. This is by far the most important aspect of exceptions, as it enables automatically analysing and aggregating them in large systems, to eg. attribute blame from changes in error metrics to individual commits.

- Nested exceptions ie. converting from one error system to another without losing information. Extensible with arbitrary metadata.

- An open and extensible error type hierarchy. Again, necessary in large scale systems to differentiate between eg. the cause (caller fault, callee fault aka HTTP 400/500 divide), retryable or permanently fatal, loggable etc. exceptions while also maintaining API/ABI backward/forward compatibility.

(for some of these, eg. Rust has crates for a Result-y equivalent, but a community consensus does not exist, yet...)

General-purpose exceptions simply are complicated, and any system trying to "re-invent" them will eventually run into the same problems. Over-simplifying error handling just results in less maintainable, debuggable and reliable systems.



This isn't a binary choice. In Scala, you can use Throwable or Exception as your error type with Either:

  Either[Throwable, Option[Foobar]]
The type Try[T] is essentially Either[Throwable, T]

Either[Throwable, T], Try, as well as IO from Cats Effect give you the stack traces that you expect from conventional Java style, with the superior option of programming in the monadic / "railway" style. Try also interfaces nicely with Java libraries: val result: Try[Foobar] = Try(javaFunction).


Don't agree with a single thing, especially not with the characterization that functional error handling is some kind of attempt at reinventing exceptions. But yeah, it's clear my and your camp will never agree lol. Fortunately for you, so far, your camp has mostly won, at least in the "object oriented" languages. But I think that's rapidly changing.


I am not in any sort of "camp", in fact I prefer using a mostly functional style. The above comment was based on experience working in large (~100M LoC) code bases.

As the comment clearly indicates, it is about anemic/"naive" functional error handling not being the counterpoint to general-purpose exceptions, not functional error handling vs. exceptions in general.

I do mostly prefer error handling being explicitly marked at every call site (ie. the functional style), but note that this is not always meaningfully possible in very large systems (at least beyond the notion of "I do not know exactly what errors are possible here, just propagate whatever happens" which is equivalent to regular exception handling)

And, as I already mentioned in the original, Rust does have functional solutions to some of these problems, and as other comments indicate, eg. Scala has them as well (probably even theoretically better since it can be a strict superset of the existing zero-cost exception model in the JVM).


The backtrace argument is good, but I wonder how valuable traces would be in a world that never experienced reads-of-nothing (npe, reading from undefined, reading out of bounds array, etc). Presumably this would be because of 100% use of ADTs, or maybe some other mechanism; but, even Haskell throws exceptions out of `IO a` so such a world might never be realized.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: