I wonder what are the advantages of Maybe
monad over exceptions? It looks like Maybe
is just explicit (and rather space-consuming) way of try..catch
syntax.
update Please note that I'm intentionally not mentioning Haskell.
Software Engineering Stack Exchange is a question and answer site for professionals, academics, and students working within the systems development life cycle. It only takes a minute to sign up.
Sign up to join this communityI wonder what are the advantages of Maybe
monad over exceptions? It looks like Maybe
is just explicit (and rather space-consuming) way of try..catch
syntax.
update Please note that I'm intentionally not mentioning Haskell.
Using Maybe
(or its cousin Either
which works basically the same way but lets you return an arbitrary value in place of Nothing
) serves a slightly different purpose than exceptions. In Java terms, it's like having a checked exception rather than a runtime exception. It represents something expected which you have to deal with, rather than an error you did not expect.
So a function like indexOf
would return a Maybe
value because you expect the possibility that the item is not in the list. This is much like returning null
from a function, except in a type-safe way which forces you to deal with the null
case. Either
works the same way except that you can return information associated with the error case, so it's actually more similar to an exception than Maybe
.
So what are the advantages of the Maybe
/Either
approach? For one, it's a first-class citizen of the language. Let's compare a function using Either
to one throwing an exception. For the exception case, your only real recourse is a try...catch
statement. For the Either
function, you could use existing combinators to make the flow control clearer. Here are a couple of examples:
First, let's say you want to try several functions that could error out in a row until you get one that doesn't. If you don't get any without errors, you want to return a special error message. This is actually a very useful pattern but would be a horrible pain using try...catch
. Happily, since Either
is just a normal value, you can use existing functions to make the code much clearer:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Another example is having an optional function. Let's say you have several functions to run, including one that tries to optimize a query. If this fails, you want everything else to run anyhow. You could write code something like:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Both of these cases are clearer and shorter than using try..catch
, and, more importantly, more semantic. Using a function like <|>
or optional
makes your intentions much clearer than using try...catch
to always handle exceptions.
Also note that you do not have to litter your code with lines like if a == Nothing then Nothing else ...
! The whole point of treating Maybe
and Either
as a monad is to avoid this. You can encode the propagation semantics into the bind function so you get the null/error checks for free. The only time you have to check explicitly is if you want to return something other than Nothing
given a Nothing
, and even then it's easy: there are a bunch of standard library functions to make that code nicer.
Finally, another advantage is that a Maybe
/Either
type is just simpler. There is no need to extend the language with additional keywords or control structures--everything is just a library. Since they're just normal values, it makes the type system simpler--in Java, you have to differentiate between types (e.g. the return type) and effects (e.g. throws
statements) where you wouldn't using Maybe
. They also behave just like any other user-defined type--there is no need to have special error-handling code baked into the language.
Another win is that Maybe
/Either
are functors and monads, which means they can take advantage of the existing monad control flow functions (of which there is a fair number) and, in general, play nicely along with other monads.
That said, there are some caveats. For one, neither Maybe
nor Either
replace unchecked exceptions. You'll want some other way to handle things like dividing by 0 simply because it would be a pain to have every single division return a Maybe
value.
Another problem is having multiple types of errors return (this only applies to Either
). With exceptions, you can throw any different types of exceptions in the same function. with Either
, you only get one type. This can be overcome with sub-typing or an ADT containing all the different types of errors as constructors (this second approach is what is usually used in Haskell).
Still, over all, I prefer the Maybe
/Either
approach because I find it simpler and more flexible.
OpenFile()
can throw FileNotFound
or NoPermission
or TooManyDescriptors
etc. A None does not carry this information.if None return None
-style statements.Most importantly of all, an exception and a Maybe monad have different purposes - an exception is used to signify a problem, while a Maybe isn't.
"Nurse, if there's a patient in room 5, can you ask him to wait?"
(notice the "if" - this means the doctor is expecting a Maybe monad)
None
values can just be propagated). Your point 5 is only kind of right … the question is: which situations are unambiguously exceptional? As it turns out … not many.
May 30, 2012 at 14:28
bind
in such a way that testing for None
doesn’t incur a syntactic overhead however. A very simple example, C# just overloads the Nullable
operators appropriately. No checking for None
necessary, even when using the type. Of course the check is still done (it’s type safe), but behind the scenes and doesn’t clutter your code. The same applies in some sense to your objection to my objection to (5) but I agree that it may not always apply.
May 30, 2012 at 14:54
Maybe
as a monad is to make the propagating None
implicit. This means that if you want to return None
given None
, you do not have to write any special code at all. The only time you need to match is if you want to do something special on None
. You never need if None then None
sort of statements.
May 30, 2012 at 20:23
null
checking exactly like that (e.g. if Nothing then Nothing
) for free because Maybe
is a monad. It's encoded in the definition of bind (>>=
) for Maybe
.
May 30, 2012 at 20:45
Either
) that behaves just like Maybe
. Switching between the two is actually rather simple because Maybe
is really just a special case of Either
. (In Haskell, you could think of Maybe
as Either ()
.)
May 30, 2012 at 20:56
"Maybe" is not a replacement for exceptions. Exceptions are meant to be used in exceptional cases (for instance: opening a db connection and the db server is not there although it should be). "Maybe" is for modeling a situation when you may or may not have a valid value; say you are getting a value from a dictionary for a key: it may be there or may be not - there is nothing "exceptional" about any of these outcomes.
I second Tikhon's answer, but I think there's a very important practical point that everybody's missing:
Either
mechanism isn't coupled to to threads at all.So something that we're seeing nowadays in real life is that many asynchronous programming solutions are adopting a variant of the Either
-style of error handling. Consider Javascript promises, as detailed in any of these links:
The concept of promises allows you write asynchronous code like this (taken from the last link):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
Basically, a promise is an object that:
Basically, since the language's native exception support doesn't work when your computation is happening across multiple threads, a promises implementation has to provide an error-handling mechanism, and these turn out to be monads similar to Haskell's Maybe
/Either
types.
The Haskell type system will require the user to acknowledge the possibility of a Nothing
, whereas programming languages often don't require that an exception be caught. That means that we'll know, at compile-time, that the user has checked for an error.
throws NPE
to every single signature and catch(...) {throw ...}
to every single method body. But I do believe there's a market for checked in a same sense as with Maybe: nullability is optional and tracked in the type system.
The maybe monad is basically the same as most mainstream language's use of "null means error" checking (except it requires the null to be checked), and has largely the same advantages and disadvantages.
Maybe
numbers by writing a + b
without the need to check for None
, and the result is once again an optional value.
May 30, 2012 at 14:57
Maybe
type, but using Maybe
as a monad adds syntax sugar that allows expressing null-ish logic a lot more elegantly.
Exception handling can be a real pain for factoring and testing. I know python provides nice "with" syntax that allows you to trap exceptions without the rigid "try ... catch" block. But in Java, for example, try catch blocks are big, boilerplate, either verbose or extremely verbose, and hard to break up. On top of that, Java adds all the noise around checked vs. unchecked exceptions.
If, instead, your monad catches exceptions and treats them as a property of the monadic space (instead of some processing anomaly), then you're free to mix and match functions you bind into that space regardless of what they throw or catch.
If, better yet, your monad prevents conditions where exceptions could happen (like pushing a null check into Maybe), then even better. if...then is much, much easier to factor and test than try...catch.
From what I've seen Go is taking a similar approach by specifying that each function returns (answer, error). That's sort of the same as "lifting" the function into a monad space where the core answer type is decorated with an error indication, and effectively side-stepping throwing & catching exceptions.