Three Useful Monads
Note: before reading this, you should know what a monad is. Read this post if you don't!
Here's a function half
:
And we can apply it a couple of times:
half . half $ 8
=> 2
Everything works as expected. Now you decide that you want to log what happens in this function:
half x = (x `div` 2, "I just halved " ++ (show x) ++ "!")
Okay, fine. Now what if you want to apply half a couple of times?
half . half $ 8
Here's what we want to have happen:
Spoilers: it doesn't happen automatically. You have to do it yourself:
finalValue = (val2, log1 ++ log2)
where (val1, log1) = half 8
(val2, log2) = half val1
Yuck! That's nowhere as nice as:
half . half $ 8
And what if you have more functions that log things? There's a pattern here: for each function that returns a log along with a value, we want to combine those logs. This is a side-effect, and monads are great at side effects!
The Writer monad
The Writer monad is cool. "Hey dude, I'll handle the logging," says Writer. "Go back to your clean code and crank up some Zeppelin!"
Every writer has a log and a return value:
data Writer w a = Writer { runWriter :: (a, w) }
Writer lets us write code like this:
half 8 >>= half
Or you can use the <=<
function, which does function composition with monads, to get:
half <=< half $ 8
which is pretty darn close to half . half $ 8
. Cool!
You use tell
to write something to the log. And return
puts a value in a Writer. Here's our new half
function:
half :: Int -> Writer String Int
half x = do
tell ("I just halved " ++ (show x) ++ "!")
return (x `div` 2)
It returns a Writer
:
And we can use runWriter
to extract the values from the Writer
:
runWriter $ half 8
=> (4, "I just halved 8!")
But the cool part is, now we can chain calls to half
with >>=
:
runWriter $ half 8 >>= half
=> (2, "I just halved 8!I just halved 4!")
Here's what's happening:
>>=
magically knows how to combine two writers, so we don't have to write any of that tedious code ourselves! Here's the full definition of >>=
:
Which is the same boilerplate code we had written before. Except now, >>=
takes care of it for us. Cool! We also used return
, which takes a value and puts it in a monad:
return val = Writer (val, "")
(Note: these definitions are almost right. The real Writer
monad allows us to use any Monoid
as the log, not just strings. I have simplified it here a bit).
Thanks, Writer monad!
The Reader Monad
Suppose you want to pass some config around to a lot of functions. Use the Reader
monad:
The reader monad lets you pass a value to all your functions behind the scenes. For example:
greeter :: Reader String String
greeter = do
name <- ask
return ("hello, " ++ name ++ "!")
greeter
returns a Reader monad:
Here's how Reader is defined:
data Reader r a = Reader { runReader :: r -> a }
Reader was always the renegade. The wild card. Reader is different because it's
only field is a function, and this is confusing to look at. But we both
understand that you can use runReader
to get that function:
And then you give this function some state, and it's used in greeter
:
runReader greeter $ "adit"
=> "hello, adit!"
So when you use >>=
, you should get a Reader
back. When you pass in a state to that reader, it should be passed through to every function in that monad.
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
Reader always was a little complex. The complex ones are the best.
return
puts a value in a Reader
:
return a = Reader $ \_ -> a
And finally, ask
gives you back the state that was passed in:
ask = Reader $ \x -> x
Want to spend some more time with Reader
? Turn up the punk rock and see this longer example.
The State Monad
The State monad is the Reader monad's more impressionable best friend:
She's exactly like the Reader monad, except you can write as well as read!
Here's how State
is defined:
State s a = State { runState :: s -> (a, s) }
You can get the state with get
, and change it with put
. Here's an example:
greeter :: State String String
greeter = do
name <- get
put "tintin"
return ("hello, " ++ name ++ "!")
runState greeter $ "adit"
=> ("hello, adit!", "tintin")
Nice! Reader was all like "you won't change me", but State is committed to this relationship and willing to change.
The definitions for the State
monad look pretty similar to the definitions for the Reader
monad:
return
:
return a = State $ \s -> (a, s)
>>=
:
m >>= k = State $ \s -> let (a, s') = runState m s
in runState (k a) s'
Conclusion
Writer. Reader. State. You added three powerful weapons to your Haskell arsenal today. Use them wisely.
Translations
This post has been translated into:
Human languages:
If you translate this post, send me an email and I'll add it to this list!