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.
Best Answer
Reading the documentation on F#'s
Option
type, it looks like it does behave pretty much exactly like theMaybe
type in Haskell, in that it can model either 'nothing' (None
in F#,Nothing
in Haskell), or a value of its argument type (Some
in F#,Just
in Haskell).In Haskell, however,
Maybe
is also a monad, and the plumbing is such that it allows for calculations onMaybe
values, early-returningNothing
if any of the variables in the calculation isNothing
. Used this way,Maybe
is a simple error handler (or rather, error-ignoring device), and the fact that it is a monad allows moving the boilerplate out of the way. Look at this wikipedia article for a nice concise example. I don't thinkOption
supports this kind of monadic usage (in fact, I'm wondering whether there is any explicit concept of a monad in F# at all). If you want this behavior in .NET, I guess you'd useOption.Value
for all your arguments, and wrap the whole calculation in a try / catch onNullReferenceException
.So, while
Option
is similar to theMaybe
type, it is not an equivalent to theMaybe
monad.