I understand the advantage of IO monad and List Monad over Functor, however, I don't understand the advantage of Option/Maybe Monad over Functor.
Is that simply the integration of types of the language?
or
What is the advantage of Option/Maybe Monad over Functor in specific use?
PS. asking the advantage in specific use is not opinion-based because if there is, it can be pointed out without subjective aspect.
PS.PS. some member here is so eager to push repeatedly
Option is both a functor and a monad?
should be the answer, or QA is duplicate, but actually not.
I already know the basics such as
Every monad is an applicative functor and every applicative functor is a functor
as the accepted answer there, and that is not what I'm not asking here.
Having an excellent answer here that is Not included in the previous QA.
The aspect, detail, or resolution of each question is quite different, so please avoid "bundling" different things in rough manner here.
CodePudding user response:
Let's look at the types.
fmap :: Functor f => (a -> b) -> (f a -> f b)
(<*>) :: Applicative f => f (a -> b) -> (f a -> f b)
flip (>>=) :: Monad f => (a -> f b) -> (f a -> f b)
For a functor, we can apply an ordinary function a -> b
to a value of type f a
to get a value of type f b
. The function never gets a say in what happens to the f
part, only the inside. Thinking of functors as sort of box-like things, the function in fmap
never sees the box itself, just the inside, and the value gets taken out and put back into the exact same box it started at.
An applicative functor is slightly more powerful. Now, we have a function which is also in a box. So the function gets a say in the f
part, in a limited sense. The function and the input each have f
parts (which are independent of each other) which get combined into the result.
A monad is even more powerful. Now, the function does not, a priori, have an f
part. The function takes a value and produces a new box. Whereas in the Applicative
case, our function's box and our value's box were independent, in the Monad
case, the function's box can depend on the input value.
Now, what's all this mean? You've asked me to focus on Maybe
, so let's talk about Maybe
in the concrete.
fmap :: (a -> b) -> (Maybe a -> Maybe b)
(<*>) :: Maybe (a -> b) -> (Maybe a -> Maybe b)
flip (>>=) :: (a -> Maybe b) -> (Maybe a -> Maybe b)
As a reminder, Maybe
looks like this.
data Maybe a = Nothing | Just a
A Maybe a
is a value which may or may not exist. From a functor perspective, we'll generally think of Nothing
as some form of failure and Just a
as a successful result of type a
.
Starting with fmap
, the Functor
instance for Maybe
allows us to apply a function to the inside of the Maybe
, if one exists. The function gets no say over the success or failure of the operation: A failed Maybe
(i.e. a Nothing
) must remain failed, and a successful one must remain successful (obviously, we're glossing over undefined
and other denotational semantic issues here; I'm assuming that the only way a function can fail is with Nothing
).
Now (<*>)
, the applicative operator, takes a Maybe (a -> b)
and a Maybe a
. Either of those two might have failed. If either of them did, then the result is Nothing
, and only if the two both succeeded do we get a Just
as our result. This allows us to curry operations. Concretely, if I have a function of the form g :: a -> b -> c
and I have values ma :: Maybe a
and mb :: Maybe b
, then we might want to apply g
to ma
and mb
. But when we start to do that, we have a problem.
fmap g ma :: Maybe (b -> c)
Now we've got a function that may or may not exist. We can't fmap
that over mb
, because a nonexistent function (a Nothing
) can't be an argument to fmap
. The problem is that we have two independent Maybe
values (ma
and mb
in our example) which are fighting, in some sense, for control. The result should only exist if both are Just
. Otherwise, the result should be Nothing
. It's sort of a Boolean "and" operation, in that if any of the intermediates fail, then the whole calculation fails. (Note: If you're looking for a Boolean "or", where any individual success can recover from prior failure, then you're looking for Alternative
)
So we write
(fmap g ma) <*> mb :: Maybe c
or, using the more convenient synonym Haskell provides for this purpose,
g <$> ma <*> mb :: Maybe c
Now, the key word in the above situation is independent. ma
and mb
have no say over the other's success or failure. This is good in many cases, because code like this can often be parallelized (there are very efficient command line argument parsing libraries that exploit just this property of Applicative
). But, obviously, it's not always what we want.
Enter Monad
. In the Maybe
monad, the provided function produces a value of type Maybe b
based on the input a
. The Maybe
part of the a
and of the b
are no longer independent: the latter can depend directly on the former.
For example, take the classic example of Maybe
: a square root function. We can't take a square root of a negative number (let's assume we're not working with complex numbers here), so our hypothetical square root looks like
sqrt :: Double -> Maybe Double
sqrt x | x < 0 = Nothing
| otherwise = Just (Prelude.sqrt x)
Now, suppose we've got some number r
. But r
isn't just a number. It came from earlier in our computation, and our computation might have failed. Maybe it did a square root earlier, or tried to divide by zero, or something else entirely, but it did something that has some chance of producing a Nothing
. So r
is Maybe Double
, and we want to take its square root.
Obviously, if r
is already Nothing
, then its square root is Nothing
; we can't possibly take a square root if we've already failed to compute everything else. On the other hand, if r
is a negative number, then sqrt
is going to fail and produce Nothing
despite the fact that r
is itself Just
. So what we really want is
case r of
Nothing -> Nothing
Just r' -> sqrt r'
And this is exactly what the Monad
instance for Maybe
does. That code is equivalent to
r >>= sqrt
The result of this entire computation (and, namely, whether or not it is Nothing
or Just
) depends not just on whether or not r
is Nothing
but also on r
's actual value. Two different Just
values of r
can produce success or failure depending on what sqrt
does. We can't do that with just a Functor
, we can't even do that with Applicative
. It takes a Monad
.