Learn You a Haskell for Great Good! - Miran Lipovaca [142]
Functions As Monads
Not only is the function type (->) r a functor and an applicative functor, but it’s also a monad. Just like other monadic values that you’ve met so far, a function can also be considered a value with a context. The context for functions is that that value is not present yet and that we need to apply that function to something in order to get its result.
Because you’re already acquainted with how functions work as functors and applicative functors, let’s dive right in and see what their Monad instance looks like. It’s located in Control.Monad.Instances, and it goes a little something like this:
instance Monad ((->) r) where
return x = \_ -> x
h >>= f = \w -> f (h w) w
You’ve seen how pure is implemented for functions, and return is pretty much the same thing as pure. It takes a value and puts it in a minimal context that always has that value as its result. And the only way to make a function that always has a certain value as its result is to make it completely ignore its parameter.
The implementation for >>= may seem a bit cryptic, but it’s really not all that complicated. When we use >>= to feed a monadic value to a function, the result is always a monadic value. So, in this case, when we feed a function to another function, the result is a function as well. That’s why the result starts off as a lambda.
All of the implementations of >>= so far somehow isolated the result from the monadic value and then applied the function f to that result. The same thing happens here. To get the result from a function, we need to apply it to something, which is why we use (h w) here, and then we apply f to that. f returns a monadic value, which is a function in our case, so we apply it to w as well.
The Reader Monad
If you don’t get how >>= works at this point, don’t worry. After a few examples, you’ll see that this is a really simple monad. Here’s a do expression that utilizes it:
import Control.Monad.Instances
addStuff :: Int -> Int
addStuff = do
a <- (*2)
b <- (+10)
return (a+b)
This is the same thing as the applicative expression that we wrote earlier, but now it relies on functions being monads. A do expression always results in a monadic value, and this one is no different. The result of this monadic value is a function. It takes a number, then (*2) is applied to that number, and the result becomes a. (+10) is applied to the same number that (*2) was applied to, and the result becomes b. return, as in other monads, doesn’t have any effect but to make a monadic value that presents some result. This presents a+b as the result of this function. If we test it, we get the same result as before:
ghci> addStuff 3
19
Both (*2) and (+10) are applied to the number 3 in this case. return (a+b) does as well, but it ignores that value and always presents a+b as the result. For this reason, the function monad is also called the reader monad. All the functions read from a common source. To make this even clearer, we can rewrite addStuff like so:
addStuff :: Int -> Int
addStuff x = let
a = (*2) x
b = (+10) x
in a+b
You see that the reader monad allows us to treat functions as values with a context. We can act as if we already know what the functions will return. It does this by gluing functions together into one function and then giving that function’s parameter to all of the functions that compose it. So, if we have a lot of functions that are all just missing one parameter, and they will eventually be applied to the same thing, we can use the reader monad to sort of extract their future results, and the >>= implementation will make sure that it all works out.
Tasteful Stateful Computations
Haskell is a pure language, and because of that, our programs are made of functions that can’t change any global state or variables; they can only do some computations and return