I also blog frequently on the Yesod Web Framework blog, as well as the FP Complete blog.

Monads are like Lannisters

September 12, 2016

See a typo? Have a suggestion? Edit this page on Github

As many people are likely aware, monads (incorrectly) have a bad rap in the programming community for being difficult to learn. A string of extremely flawed monad tutorials based on analogies eventually led to a blog post by Brent Yorgey about the flaws of this analogy-based approach. And we've seen great learning materials on Haskell and monads.

However, I'm disappointed to see the analogy-based route disappear. Based on a recent Twitter poll I ran, which is held to the highest levels of scientific and statistical scrutiny, it's obvious that there is very high demand for a rigorous analogy-based monad tutorial. I claim that the flaw in all previous analogy based tutorials is lack of strong pop culture references. Therefore, I'm happy to announce the definitive guide to monads: Monads are like Lannisters.

Spoiler alert: if you haven't completed book 5 or season 6 of Haskell yet, there will be spoilers.

Prereqs

The examples below will all be Haskell scripts that can be run with the Stack build tool. Please grab Stack to play along. Copy-paste the full example into a file like foo.hs and then run it with stack foo.hs. (This uses Stack's script interpreter support.)

Hear me roar

Many people believe that the Lannister house words are "A Lannister always pays his debts" (or her debts of course). We'll get to that line in a moment. This belief however is false: the true Lannister house words are Hear me roar. So let's hear a Lannister roar:

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

roar :: IO ()
roar = putStrLn "Roar"

main :: IO ()
main = do
    roar -- Tywin
    roar -- Cersei
    roar -- Jaime
    roar -- Tyrion

Roaring is clearly an output, and therefore it makes sense that our action is an IO action. But roaring doesn't really do much besides making sound, so its return is the empty value (). In our main function, we use do notation to roar multiple times. But we can just as easily use the replicateM_ function to replicate a monadic action multiple times and discard (that's what the _ means) the results:

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

import Control.Monad (replicateM_)

roar :: IO ()
roar = putStrLn "Roar"

main :: IO ()
main = replicateM_ 4 roar

Tyrion, the scholar

As we all know, Tyrion is a prolific scholar, consuming essentially any book he can get his hands on (we'll discuss some other consumption next). Fortunately, monads are there to back him up, with the Reader monad. Let's say that Tyrion is doing some late night research on wine production (epic foreshadowment) in various kingdoms, and wants to produce a total:

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

import Data.Map (Map)
import qualified Data.Map as Map
import Control.Monad.Trans.Reader
import Data.Maybe (fromMaybe)

type Kingdom = String
type WineCount = Int
type WineData = Map Kingdom WineCount

tyrionResearch :: Reader WineData Int
tyrionResearch = do
    mnorth <- asks $ Map.lookup "north"
    mriverlands <- asks $ Map.lookup "riverlands"
    return $ fromMaybe 0 mnorth + fromMaybe 0 mriverlands

main :: IO ()
main = print $ runReader tyrionResearch $ Map.fromList
    [ ("north", 5)
    , ("riverlands", 10)
    , ("reach", 2000)
    , ("dorne", 1000)
    ]

While Tyrion may have chosen inferior kingdoms for wine production, it does not take away from the fact that the Reader type has allowed him access to data without having to explicitly pass it around. For an example this small (no dwarf joke intended), the payoff isn't great. But Reader is one of the simplest monads, and can be quite useful for larger applications.

Tyrion, the drinker

Let's try another one. We all know that Tyrion also likes to drink wine, not just count it. So let's use our Reader monad to give him access to a bottle of wine.

However, unlike reading data from a book, drinking from a bottle actually changes the bottle. So we have to leave our pure Reader world and get into the ReaderT IO world instead. Also known as monad transformers. (Unfortunately I don't have time now for a full post on it, but consider: Transformers: Monads in Disguise.)

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

import Control.Monad (replicateM_)
import Control.Monad.Trans.Reader
import Control.Monad.IO.Class
import Control.Concurrent.Chan

data Wine = Wine
type Bottle = Chan Wine

drink :: MonadIO m => Bottle -> m ()
drink bottle = liftIO $ do
    Wine <- readChan bottle
    putStrLn "Now I'm slightly drunker"

tyrionDrinks :: ReaderT Bottle IO ()
tyrionDrinks = replicateM_ 10 $ ReaderT drink

main :: IO ()
main = do
    -- Get a nice new bottle
    bottle <- newChan

    -- Fill up the bottle
    replicateM_ 20 $ writeChan bottle Wine

    -- CHUG!
    runReaderT tyrionDrinks bottle

A Lannister always pays his debts

What is a debt, but receiving something from another and then returning it? Fortunately, there's a monad for that too: the State monad. It lets us take in some value, and then give it back - perhaps slightly modified.

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

import Control.Monad.Trans.State

type Sword = String

forge :: State Sword Sword
forge = do
    "Ice" <- get
    -- do our Valyrian magic, and...

    -- Repay our debt to Brienne
    put "Oathkeeper"

    -- And Tywin gets a sword too!
    return "Widows Wail"

killNedStark :: IO Sword
killNedStark = do
    putStrLn "Off with his head!"
    return "Ice"

main :: IO ()
main = do
    origSword <- killNedStark

    let (forTywin, forBrienne) = runState forge origSword

    putStrLn $ "Tywin received: " ++ forTywin
    putStrLn $ "Jaime gave Brienne: " ++ forBrienne

Not exactly justice, but the types have been satisfied! A monad always pays its debts.

Throwing

Monads are also useful for dealing with exceptional cases:

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

import Control.Exception
import Data.Typeable (Typeable)

data JaimeException = ThrowBranFromWindow
    deriving (Show, Typeable)
instance Exception JaimeException

jaime :: IO ()
jaime = do
    putStrLn "Did anyone see us?"
    answer <- getLine
    if answer == "no"
        then putStrLn "Good"
        else throwIO ThrowBranFromWindow

main :: IO ()
main = jaime

Killing

And thanks to asynchronous exceptions, you can also kill other threads with monads.

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.Typeable (Typeable)

data JaimeException = Defenestrate
    deriving (Show, Typeable)
instance Exception JaimeException

bran :: IO ()
bran = handle onErr $ forever $ do
    putStrLn "I'm climbing a wall!"
    threadDelay 100000
  where
    onErr :: SomeException -> IO ()
    onErr ex = putStrLn $ "Oh no! I've been killed by: " ++ show ex

jaime :: ThreadId -> IO ()
jaime thread = do
    threadDelay 500000
    putStrLn "Oh, he saw us"
    throwTo thread Defenestrate
    threadDelay 300000
    putStrLn "Problem solved"

main :: IO ()
main = do
    thread <- forkIO bran
    jaime thread

Exercise for the reader: modify bran so that he properly recovers from that exception and begins warging instead. (WARNING: don't recover from async exceptions in practice.)

Exiting

You can also exit your entire process with monads.

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

import System.Exit

tommen :: IO ()
tommen = do
    putStrLn "Oh, my dear wife!"
    exitFailure

main :: IO ()
main = tommen

Passing the baton of reign

We've seen Lannisters pass the baton of reign from family member to family member. We've also seem them ruthlessly destroy holy insitutions and have their private, internal affairs exposed. As it turns out, we can do all of that with some explicit state token manipulation!

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc

{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
import GHC.Prim
import GHC.Types

joffrey :: IO ()
joffrey = do
    putStrLn "Look at your dead father's head Sansa!"
    putStrLn "Oh no, poisoned!"

tommen :: IO ()
tommen = do
    putStrLn "I'm in love!"
    putStrLn "*Swan dive*"

cersei :: IO ()
cersei = undefined -- season 7 isn't out yet

unIO :: IO a -> State# RealWorld -> (# State# RealWorld, a #)
unIO (IO f) = f

main :: IO ()
main = IO $ \s0 ->
  case unIO joffrey s0 of
    (# s1, () #) ->
      case unIO tommen s1 of
        (# s2, () #) ->
          unIO cersei s2

Honorable mentions

There's much more to be done with this topic, and there were other ideas besides Lannisters for this post. To give some mention for other ideas:

  • There's plenty of other Lannister material to work with (skinning animals, Jaime and Cersei affairs, or Tyrion proclivities). But I had to draw the line somewhere (both for length of post and topics I felt like discussing...). Feel free to comment about other ideas.
  • One of my favorites: monads are like onions
  • Monads are just your opinion, man
  • "they're like Gargoyles, they decorate the public facing interface of important landmarks in Haskell-land." link

Personally, I was going to go with a really terrible "monads are like printers" based on them taking plain paper in and spitting out printed pages, but Lannisters was a lot more fun. Also, I'm sure there's something great to be done with presidential candidates, at the very least throwTo opponent Insult.

Disclaimer

Since I'm sure someone is going to get upset about this: YES, this post is meant entirely as parody, and should not be used for actually learning anything except some random details of Game of Thrones, and perhaps a bit of Haskell syntax. Please use the learning materials I linked at the beginning for actually learning about Haskell and monads. And my real advice: don't actually "learn monads," just start using Haskell and you'll pick them up naturally.

Blog archive