4 ms·
>f(g(x)); >Exceptions make this (and similar) patterns suffer so badly. Why one would want to pass an error as an input to a function that expects valid data?
by protomolecule 2y ago
>f(g(x));
>Exceptions make this (and similar) patterns suffer so badly.
Why one would want to pass an error as an input to a function that expects valid data?
- Expurple 2y agoThat's the thing! In this example, `f` doesn't expect valid data, by design. Some functions are responsible for figuring out what to do in case of unsuccessful operations. Maybe `f` would be easier to imagine as a simple mechanical refactoring where you extract a large `try-catch` block into a separate generic helper function. It's more convenient and abstract to make it accept a single `Result` value, rather than the entire list of arguments to call `g`. Sometimes `f` is not supposed to be responsible for calling `g` or even know about `g` and its arguments.
- protomolecule 2y agoBut why design such functions? And when you do need it, isn't 'the error' just a case of valid data that shouldn't be thrown as exception at all?
- Expurple 2y ago> isn't 'the error' just a case of valid data that shouldn't be thrown as exception at all? That's the thing! Errors are just a case of return data! To me, it just makes more sense to model them like that rather than like checked exceptions. Checked exceptions are simply unnecessary if the language has sum types, Result and some syntax like Rust's `?` for propagating errors easily like exceptions. Checked exceptions are a weird, special and sometimes poorly supported [1] way to "augment the return type". It's easier to just have a single, all-encompassing, composable return type. [1] https://home.expurple.me/posts/rust-solves-the-issues-with-exceptions/#fnref:3 https://home.expurple.me/posts/rust-solves-the-issues-with-e...
- protomolecule 2y ago>Errors are just a case of return data! Are they? Why mix valid data with errors?
- Expurple 2y agoThey aren't necessarily mixed together in an unmaintainable way. In a Rust-like functional paradigm, fallible functions typically return Result<T, E> and you can extract the T out of it and proceed to work on just the valid T when you've already handled the errors. Just like you would do in a language with exceptions.
- protomolecule 2y agoAnd the function has to do pattern matching and handle both cases. What for?
- Expurple 2y agoIt's equivalent to having to "catch or specify" [1]. IMO, it's a lesser evil than unchecked exceptions [2]. Explicit and mandatory handling is desirable for most errors. The caller has the responsibility to decide whether it needs to handle the error. If it simply wants to propagate it, there's the `?` operator. If it doesn't care about defining a wrapper type and/or preserving the error details, there's `Box<dyn Error>` (equivalent to `throws Exception`). Explicit and reliable error handling usually doesn't come at the cost of writing full `match` statements everywhere. [1] https://docs.oracle.com/javase/tutorial/essential/exceptions/catchOrDeclare.html https://docs.oracle.com/javase/tutorial/essential/exceptions... [2] https://home.expurple.me/posts/rust-solves-the-issues-with-exceptions/#unchecked-exceptions https://home.expurple.me/posts/rust-solves-the-issues-with-e...
- protomolecule 2y agoI don't see why it is a 'lesser evil'. "Explicit and mandatory handling is desirable for most errors." -- that depends on the domain. Quite often all the caller needs is to cancel whatever it is doing and propagate the error. In this case why do we need a detour into the f() function in your example? From the part of your essay that you linked to: "Potentially leaving your data in an inconsistent, half-way modified state." -- this is just not the case with RAII. "No one wraps every line in a try-catch." -- no one needs to.