Saying "I prefer Maybe to try/catch" is too broad - it's almost like saying you prefer not to handle errors.
Saying "I prefer Maybe to try/catch" is too broad - it's almost like saying you prefer not to handle errors.
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.
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?
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
[2]: https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36...
[3]: (a little wordy) https://www.joelonsoftware.com/2005/05/11/making-wrong-code-...
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.
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).
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.