_______ __ _______
| | |.---.-..----.| |--..-----..----. | | |.-----..--.--.--..-----.
| || _ || __|| < | -__|| _| | || -__|| | | ||__ --|
|___|___||___._||____||__|__||_____||__| |__|____||_____||________||_____|
on Gopher (inofficial)
URI Visit Hacker News on the Web
COMMENT PAGE FOR:
URI Fault Report â and alternative to Result in F#
daxfohl wrote 21 hours 35 min ago:
Main 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 wrote 21 hours 30 min ago:
Au 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 wrote 20 hours 59 min ago:
Oh, 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.
akdor1154 wrote 1 day ago:
I'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. :)
gf000 wrote 1 day 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.
neonsunset wrote 1 day ago:
Go 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
EdwardDiego wrote 1 day ago:
It'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.
smoothdeveloper wrote 1 day ago:
/!\ this is just my perspective /!\
> would love to know why leaving it unbounded was decided upon. Might
be I don't quite grok the culture of F#.
You may look at more context in the discussions pertaining to the
RFC: [1] I think those are the main factors:
* "Keep It Simple Stupid" principle
* community pressure so that F# libraries could standardise on better
things that Choice1Of2 = Ok and Choice2Of2 = Error, or using a bare
tuple (which is very not clean for APIs)
* F# ought to remain flexible in terms of not pretending of totally
abstracting away the fact that most of the dotnet base class library
or the overall dotnet ecosystem, uses exceptions, it is not like
Result type was created to abstract runtime exceptions away.
For all other needs, one is just one wrapper / or abstraction (for
library that don't "meet our standards" in terms of exception safety)
away, along with a bit of adhoc tooling around leveraging
FSharp.Compiler.Service for sake of adding static analysis, for
architecture astronauts, dealing with behemoth or safety critical
codebases, requiring even more screws turned, but even there, F# (and
so many other languages) may not meet the bar if those are the
critical properties for a project involving code.
Overall, F# is just pragmatic for the 99.99%, and not trying too hard
beside the steady core language idioms, and how the organic community
wants it over time, to satisfy hypothetical and intellectual pursuits
of the highest abstraction and safety.
The culture of F# is mostly about throwing productivity parties and
being done with it (while not suffering so many of the wrong choices
done by C, C++, Java and it's famed sibbling, C#), rather than the
higher conceptual type theory perfection that (sometimes ?) lead to
code that is not less kludgy, or easier to maintain, in the grand
scheme, for the masses of developers, that are not yet expert in
Haskell, Scala, etc.
It is probably not too dissimilar to OCaml culture, while embracing
some aspects that are familiar to more people, due to relationship
with dotnet ecosystem.
URI [1]: https://github.com/fsharp/fslang-design/blob/main/FSharp-4.1...
neonsunset wrote 1 day ago:
C# is not a Java sibling :(
(There are languages more similar to C# than Java and vice versa,
and of course the underlying type system differences between JVM
and .NET also add to this rift, C# was intended as a successor to
C++ just as much as it was to Java)
psd1 wrote 1 day ago:
They are not identical twins nor byte-compatible. Collect your
prize on the way out.
EdwardDiego wrote 1 day ago:
Thank you, I'll read your link, but really appreciate your
perspective. :)
Guvante wrote 1 day ago:
Rust 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 wrote 1 day ago:
Oh true, I am really interested in those choices too.
remexre wrote 1 day ago:
The case that comes most to mind for that in Rust is
URI [1]: https://doc.rust-lang.org/std/primitive.slice.html#metho...
EdwardDiego wrote 1 day ago:
Thank you!
omcnoe wrote 1 day ago:
As 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.
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.
nozzlegear wrote 19 hours 35 min ago:
I'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.
needlesslygrim wrote 1 day ago:
I 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.
DIR <- back to front page