5 ms·
I much prefer Maybe types to try/catch.
by presentation 2y ago
I much prefer Maybe types to try/catch.
- happytoexplain 2y agoThey're not really comparable. They serve different purposes. Maybe/Optional is for when something might not be, while try/catch is a powerful system for passing both user-facing and machine-readable information about errors across potentially multiple layers of API. A powerful language has both. Saying "I prefer Maybe to try/catch" is too broad - it's almost like saying you prefer not to handle errors.
- presentation 2y agoWell, you handle errors by needing to explicitly handle them at the point of error, as opposed to all callers needing to anticipate and understand every random system error from 10 levels deep of APIs (or just throw them all away).
- XorNot 2y agoExcept this isn't my experience of errors at all. My experience of errors is that most of the time you need more scope in order to handle them properly. I.e. if writing to a file happens, what am I actually supposed to do about that? Well, that depends entirely on why I'm writing to a file. Which might not be information the calling function actually has. Usually I need to go up the call stack to find somewhere which has enough context to know what's actually not working because a file write has failed. Basically, the vast majority of my code can always error or depends on things which can: or is one refactor away from now needing to return errors. If just about everything is Result<T>, then really why should anything be? Why eat the syntactic noise?
- Mawr 2y agoOf course you think the majority of your code can error - exceptions don't let you draw a distinction between fallible and infallible code, so you have to assume all code can fail. This leads to two problems: 1. You can't take advantage of infallible code... Infallible code is much easier to use because there is no need to worry about errors. But you can't take advantage of that because infallibility is not encoded in any way. With Result types, infallible code is clearly marked, which allows for sectioning it off from fallible code. Programmers naturally gravitate towards doing just that because they want to take advantage of the ease of use. The result is a lot of pure functions and as little fallible code as possible. 2. ...but you will erroneously try to do so anyway You will inevitably start assuming that certain code can't possibly fail. The ease of use of infallible code is just too alluring: [1] [2] [3]. [1]: https://nedbatchelder.com//blog/202001/bug_915_solved.html https://nedbatchelder.com//blog/202001/bug_915_solved.html [2]: https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36693 https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36... [3]: (a little wordy) https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/ https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...
- XorNot 2y agoI don't care about code which "can't error", because it can't error. The only code I care about is code which can error, or worse, code which is expanding due to a requirements change and is about to make the transition to "needs to be able to return errors". There are vanishingly few business logic processes that can't error, because every bit of disk IO, network IO and user input can always throw an error that needs to be handled. What I am saying is that the benefits of pure functions are obvious, but the number of practical applications of them is far smaller then their advocates claim. They don't save me from the fact that the network may be down, the target device might crash, someone might knock the power out at the other end, or the network card picks that moment to corrupt a packet. What is the point of spending a lot of syntax on Result<T> types returning all over the place, rather then just throwing a try-except around the places in my code I know I can reasonably bound and define what the recovery process is if an error does occur. Which for every non-toy example, is a guarantee.
- erik_seaberg 2y agoInfallible code is too rare to be worth any effort. Even math can fail (though most platforms are stuck with legacy decisions to quietly return wrong answers). And you always have to recover from a timeout due to a downstream failure in some of the cheap commodity hardware that's running your code.
- brabel 2y agoI tend to agree with you. Rust uses Result types and for any real-world API, you can be almost certain that 90% of operations can fail and need to return Result. You can see that in most Rust code that's not just algorithms, you will have a `?` at the end of nearly every method (which is the way Rust propagates errors, similar to how Exceptions are automatically propagated in most languages - but they don't require the `?` to mark them). Rust kind of cheated a bit with arithmetic and it doesn't return a Result when you use division, for example (it's cheating because if not for convenience, it really should return Result as arithmetic errors like division by zero and reaching a NaN are fairly common - and hence would justify that).
- zacmps 2y agoThey might have meant a type like `Result<T,E>`
- presentation 2y agoThis is what I meant!
- xyzzy_plugh 2y agoYou have this exactly backwards as far as I am concerned. Exceptions might seem like they force you to handle errors, but in practice they force you to, at some level, not handle them. I've never seen a serious, production-serving tome of code that doesn't end up generically catching all exceptions at the top and logging them blindly because all context has been lost. If you were forced to handle exceptions at every call site I might find myself able to agree, but then you have something functionally equivalent to options. I'd much rather just start with that approach.
- brabel 2y agoNo, Exceptions would be equivalent not to Options, but to Either where the left or right (depending on which tradition you follow) side is the error (which people normally have a special name for, Result). If you just use Optional then you cannot know why the operation failed, which is extremely important to any system as the error may not be recoverable and you need to report it properly, not just say "operation failed".
- wongarsu 2y agoExtend the Maybe to an Either, or an Result<T, E>, that carries either the result or the error information and you have the same thing. Add some syntactic sugar to either get the success result or propagate the error (under the condition that the current function returns a Result and there is a way to cast the inner error type into the outer error type) and you have a nice alternative to try/catch that can entirely replace it
- munchler 2y agoResult (aka Either) and Maybe are much better than exceptions for parsing. Exceptions should only be used when something truly exceptional occurs (e.g. out of disk space, no network connection). Ill-formed input is not exceptional when you're parsing a URL.
- mrkeen 2y agoNot only are they comparable, they're so comparable that one ought to put them under the same interface. If you write library code, fail polymorphically, then the client can choose whether they want to call it as Maybe/Either/Exception and we can stop these never-ending arguments.
- zarzavat 2y agoThey are not quite orthogonal but at least independent. Maybe/Either/Result is a way to represent partial computations at runtime. try/catch is a syntax to detect partial computations and to do something conditionally if one occurs. You could have a language that used try/catch syntax to catch Result types, indeed Rust does something like this with the try operator although it’s only try/catch if you squint very hard.
- spoiler 2y agoYou can do something conditionally with the monadic types too; you branch/match on them.
- zarzavat 2y agoYes, but what I’m saying is that you can also handle monadic types using try/catch syntax. Consider this imaginary language, the bastard child of Rust and Swift: fn returnsAResult() -> Result<i8, Error> { return rand(0,1) ? Ok(4) : Err(BadLuckError) } do { let x = try returnsAResult() } catch e { print(e) } This language doesn’t exist, but it could. Results are in-band errors where the error is a normal return value. Swift by contrast uses out-of-band errors. Try/catch is one particular syntax for detecting errors. Match syntax is another. In principle there’s nothing preventing match syntax being used with out-of-band errors, even with Java-like exceptions. It would look something like this: match (iCanThrow()) { case 42 -> … catch e -> … _ -> … } I like this one less than the first one though.