4 ms·
Fault Report – and alternative to Result in F#
- omcnoe 2y agoAs a daily user of F# I'm not sold on this suggested alternative. It seems to be trying to make it as easy as possible to make the Result type behave like exception handling, but I think there are some key downsides to the approach. First, Result is often used in place of Choice only to communicate the intent that the 2nd case is an error case. Making it more work to use Result (by requiring the types used for the Error to implement some special interface) means programmers will simply use Choice instead. But the bigger issue is that this suggested change would remove the ability to ensure that cases of an error type are exhaustively matched. They introduce the FailAs active pattern at the very end of the blog to deal with logic that wants to inspect the underlying error cases, but active patterns break the compilers ability to check if a match is exhaustive. This is a really useful property for program correctness, I can ensure that a caller of my function will receive a compiler error when a new error case is introduced that wasn't previously handled. I wonder if a better design for the error type could be some kind of error type set that could automatically collect possible error cases up the call stack, so that when confronted with an IFault you at least know what possible cases it could have. With some type inference help it might not even be that painful to deal with, the first example could automatically infer a type like Errors<AtlasError, ForExError>. As touched on briefly in the article, the elephant in the room here is that changing the Result type doesn't do anything to unify the two distinct worlds of exceptions vs error values that currently exist in F#. Dealing with obscure C# exception flow from F# has been the biggest source of subtle, hard to track down bugs in my production code.
- needlesslygrim 2y agoI agree, this doesn't seem to be much better than adding `throws Throwable` to all your methods that can fail in Java. The reason sum types are so useful for error handling is that all possible cases can be exhaustively checked against at compile time.
- nozzlegear 2y agoI'm also a daily user of F#, and like you I'm not totally sold on this alternative. However, at first glance (without using it in a real life scenario) I like it more than the Result type we have. One of the apps I have in production right now leans heavily on using Results, and the biggest pain point — besides handling exceptions from C# world — is the mapping of the different Result error types. It can get tedious, especially when you're three Results deep in a "railway" and each operation needs its own custom mapping because they use different Result types. Before reading this article, I'd decided that if I had to do it again I'd just throw exceptions, but now I'm wondering whether or not I'd play around with enforcing an IFault interface.
- EdwardDiego 2y agoIt's been a very long age since I wrote any F#, really enjoyed it at the time and it was for fun stuff, I wasn't trying to deal with errors, just things like process a prefix trie in a cool way, but does Result<'T, 'TError> really have no upper type bounds on TError? I'm assuming that's the case from the fact that he's arguing for a basic interface (Does F# support structural typing, or do you need to say that AtlasError implements IError or whatevs?), and I'm really surprised there isn't one. Huh, would love to know why leaving it unbounded was decided upon. Might be I don't quite grok the culture of F#. This reminds me of a very common and awful Python pattern, where, as there's no `message` attribute on the base error/exception classes, despite every nearly every exception or error having one, this is the normal pattern to access it: message = ex.args[0] It gets really fun when you get an IndexError because one exception had no message provided when instantiated, so then you find code like: message = ex.args[0] if ex.args else None Yech.
- Guvante 2y agoRust leaves it unrestricted. As does Haskell (although I am lying a little most just choose to use Either for Result) Rust actually gets some use out of it, for instance returning an alternative value instead of handling it as a pure failure.
- EdwardDiego 2y agoOh true, I am really interested in those choices too.
- remexre 2y agoThe case that comes most to mind for that in Rust is https://doc.rust-lang.org/std/primitive.slice.html#method.binary_search https://doc.rust-lang.org/std/primitive.slice.html#method.bi...
- EdwardDiego 2y agoThank you!
- akdor1154 2y agoI'm pretty sure this is isomorphic to Go's err, right? Report ~ res, err (well this bit not strictly, but in practice close enough) IFault ~ Err (interface over a string repr, and a way to wrap a cause Err with err.Unwrap) In my experience if you land on a design that looks like something either Go or F# does then you're probably on the right track. :)
- neonsunset 2y agoGo lets you ignore the errors and does not have tuples - only multi-variable returns so you can't not deconstruct the return value (and can't .map it as a result too). I don't think it's fair to place the two next to each other. F# also does exception handling (where it makes sense, though TryParse is more efficient): let values = ["abcd"; "1234"] let number = try int values[0] with _ -> 1337
- gf000 2y ago> In my experience if you land on a design that looks like something either Go or F# does then you're probably on the right track Well, let's just say that our experiences are vastly different then, but I had to do a double take when I saw Go mentioned as a positive from a PL design perspective.. And no, Go can literally return both a value and an error at the same time, it has possibly the worst error handling out of any "modern" language.
- daxfohl 2y agoMain thing I dislike about errors as ADTs is it forces indentation in both cases. I miss go's "iff err..." otherwise continue as normal. Yes monads can allow that, but they come with their own problems.
- Smaug123 2y agoAu contraire! You can unindent the final clause of an `if` or `match` block. Fantomas even supports it (`fsharp_experimental_keep_indent_in_branch = true`).
- daxfohl 2y agoOh, good to hear! My knowledge is about 7 years out of date at this point. I know typescript and mypy are pretty good at inferring which options of an ADT are impossible in various contexts and letting you do stuff like this, so great to hear F# has upped its game here too.
- deleted 2y ago[deleted]