Learn You a Haskell for Great Good! - Miran Lipovaca [123]
ghci> (\x -> Just (x+1)) 1
Just 2
ghci> (\x -> Just (x+1)) 100
Just 101
If we feed it 1, it evaluates to Just 2. If we give it the number 100, the result is Just 101. It seems very straightforward. But how do we feed a Maybe value to this function? If we think about how Maybe acts as an applicative functor, answering this is pretty easy. We feed it a Just value, take what’s inside the Just, and apply the function to it. If we give it a Nothing, then we’re left with a function but Nothing to apply it to. In that case, let’s just do what we did before and say that the result is Nothing.
Instead of calling it >>=, let’s call it applyMaybe for now. It takes a Maybe a and a function that returns a Maybe b, and manages to apply that function to the Maybe a. Here it is in code:
applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b
applyMaybe Nothing f = Nothing
applyMaybe (Just x) f = f x
Now let’s play with it. We’ll use it as an infix function so that the Maybe value is on the left side and the function is on the right:
ghci> Just 3 `applyMaybe` \x -> Just (x+1)
Just 4
ghci> Just "smile" `applyMaybe` \x -> Just (x ++ " :)")
Just "smile :)"
ghci> Nothing `applyMaybe` \x -> Just (x+1)
Nothing
ghci> Nothing `applyMaybe` \x -> Just (x ++ " :)")
Nothing
In this example, when we used applyMaybe with a Just value and a function, the function was simply applied to the value inside the Just. When we tried to use it with a Nothing, the whole result was Nothing. What about if the function returns a Nothing? Let’s see:
ghci> Just 3 `applyMaybe` \x -> if x > 2 then Just x else Nothing
Just 3
ghci> Just 1 `applyMaybe` \x -> if x > 2 then Just x else Nothing
Nothing
The results are just what we expected. If the monadic value on the left is a Nothing, the whole thing is Nothing. And if the function on the right returns a Nothing, the result is Nothing again. This is similar to when we used Maybe as an applicative and we got a Nothing result if there was a Nothing somewhere in the mix.
It looks like we’ve figured out how to take a fancy value, feed it to a function that takes a normal value, and return a fancy one. We did this by keeping in mind that a Maybe value represents a computation that might have failed.
You might be asking yourself, “How is this useful?” It may seem like applicative functors are stronger than monads, since applicative functors allow us to take a normal function and make it operate on values with contexts. In this chapter, you’ll see that monads, as an upgrade of applicative functors, can also do that. In fact, they can do some other cool stuff that applicative functors can’t do.
We’ll come back to Maybe in a minute, but first, let’s check out the type class that belongs to monads.
The Monad Type Class
Just like functors have the Functor type class, and applicative functors have the Applicative type class, monads come with their own type class: Monad! (Wow, who would have thought?)
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
The first line says class Monad m where. But wait, didn’t I say that monads are just beefed-up applicative functors? Shouldn’t there be a class constraint in there along the lines of class (Applicative m) = > Monad m where, so that a type must be an applicative functor before it can be made a monad? Well, there should, but when Haskell was made, it hadn’t occurred to people that applicative functors were a good fit for Haskell. But rest assured, every monad is an applicative functor, even if the Monad class declaration doesn’t say so.
The first function that the Monad type class defines is return. It