Haskell Monads Cheat Sheet
Covers the Monad typeclass, do-notation desugaring, Maybe/Either/List/IO monads, and the Reader/Writer/State monads for common effects.
The `Monad` Typeclass
Every monad implements `>>=` (bind) and `return`/`pure`, satisfying the monad laws.
class Applicative m => Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a return = pure-- Monad laws (must hold for any correct instance):-- Left identity: return a >>= f == f a-- Right identity: m >>= return == m-- Associativity: (m >>= f) >>= g == m >>= (\x -> f x >>= g)
`do`-Notation Desugaring
`do` blocks are pure syntax sugar over chained `>>=` calls.
safeDivide :: Int -> Int -> Maybe IntsafeDivide _ 0 = NothingsafeDivide x y = Just (x `div` y)compute :: Maybe Intcompute = do a <- safeDivide 10 2 b <- safeDivide a 0 -- Nothing short-circuits the rest return (a + b)-- Desugars to:compute2 :: Maybe Intcompute2 = safeDivide 10 2 >>= \a -> safeDivide a 0 >>= \b -> return (a + b)
Maybe, Either, List & IO
The four monads you'll reach for constantly, each modeling a different kind of effect.
-- Maybe: optional/failable computationlookupUser :: Int -> Maybe StringlookupUser 1 = Just "Ada"lookupUser _ = Nothing-- Either: failable computation carrying an error valueparseAge :: String -> Either String IntparseAge s = case reads s of [(n, "")] -> Right n _ -> Left ("invalid age: " ++ s)-- List: nondeterministic computation (all combinations)pairs :: [(Int, Int)]pairs = do x <- [1, 2] y <- [10, 20] return (x, y) -- [(1,10),(1,20),(2,10),(2,20)]-- IO: sequencing side effectsmain :: IO ()main = do putStrLn "What's your name?" name <- getLine putStrLn ("Hello, " ++ name)
Reader, Writer & State Monads
From `mtl`/`transformers` — thread config, logs, or mutable state without explicit plumbing.
import Control.Monad.Readerimport Control.Monad.Writerimport Control.Monad.State-- Reader: implicit read-only environmentgreeting :: Reader String Stringgreeting = do name <- ask return ("Hello, " ++ name)-- Writer: accumulate a log alongside a resultlogStep :: Writer [String] IntlogStep = do tell ["starting"] tell ["computing"] return 42-- State: threaded mutable-looking statecounter :: State Int Intcounter = do n <- get put (n + 1) return nrunState counter 0 -- (0, 1)
Monad Glossary
Terms you'll see constantly in Haskell code and docs.
- >>= (bind)- sequences a monadic value into a function producing another monadic value
- >> (then)- like bind but discards the first result: `m >> n = m >>= const n`
- return / pure- lifts a plain value into the monad
- Functor / Applicative / Monad- increasingly powerful typeclasses; Monad requires both of the others
- fmap / <$>- Functor's map, applies a pure function inside the wrapper
- <*> (ap)- Applicative's apply, for functions wrapped in the same context
- Kleisli composition (>=>)- composes two `a -> m b` functions monadically
Stacking Monad Transformers
Real programs combine effects by stacking transformers (ReaderT/StateT/ExceptT/IO) and threading through them with `lift`.
import Control.Monad.Readerimport Control.Monad.Stateimport Control.Monad.Exceptdata Config = Config { limit :: Int }type App a = ReaderT Config (StateT Int (ExceptT String IO)) astep :: App ()step = do cfg <- ask -- ReaderT layer n <- lift get -- StateT, reached via one lift when (n >= limit cfg) $ lift (lift (throwError "limit exceeded")) -- ExceptT, two lifts down lift (put (n + 1)) liftIO (putStrLn ("n = " ++ show n)) -- IO at the bottomrunApp :: Config -> Int -> App a -> IO (Either String a, Int)runApp cfg s0 app = runExceptT (runStateT (runReaderT app cfg) s0)
`MonadPlus`, `Alternative` & `guard`
Monads that support failure/choice let you prune branches with `guard` and pick the first success with `<|>`.
import Control.Monad (guard)import Control.Applicative (Alternative(..))pythagorean :: [(Int, Int, Int)]pythagorean = do c <- [1 .. 20] b <- [1 .. c] a <- [1 .. b] guard (a * a + b * b == c * c) -- MonadPlus: drops branches where False return (a, b, c)firstSuccess :: Maybe IntfirstSuccess = empty <|> Nothing <|> Just 3 <|> Just 4 -- => Just 3class Applicative f => Alternative f where empty :: f a (<|>) :: f a -> f a -> f a
`traverse`, `sequence` & `foldM`
Turn a list of monadic actions inside-out, or fold with a short-circuiting monadic step function.
import Control.Monad (foldM)validateAll :: [String] -> Either String [Int]validateAll = traverse parseAge where parseAge s = case reads s of [(n, "")] -> Right n _ -> Left ("bad age: " ++ s)allPresent :: [Maybe Int] -> Maybe [Int]allPresent = sequence -- sequence :: Monad m => [m a] -> m [a]sumUntilNegative :: [Int] -> Maybe IntsumUntilNegative = foldM step 0 where step acc x | x < 0 = Nothing -- short-circuits the whole fold | otherwise = Just (acc + x)
The `Cont` Monad & `callCC`
The continuation monad reifies "the rest of the computation"; `callCC` gives early-exit control flow similar to exceptions.
import Control.Monad.Contsquare :: Int -> Cont r Intsquare x = return (x * x)pythagoras :: Int -> Int -> Cont r Intpythagoras x y = do x2 <- square x y2 <- square y return (x2 + y2)safeDiv :: Int -> Int -> Cont r (Either String Int)safeDiv x y = callCC $ \exit -> do when (y == 0) $ exit (Left "division by zero") return (Right (x `div` y))runCont (safeDiv 10 0) id -- => Left "division by zero"
Monad Transformer Glossary
Vocabulary for reading real-world `mtl`/`transformers` code.
- lift- pushes an action from an inner monad up one level in a transformer stack
- liftIO- lifts an IO action through an arbitrary number of transformer layers to the top
- MonadTrans- the typeclass defining `lift`, implemented by ReaderT/StateT/WriterT/ExceptT etc.
- ReaderT / StateT / ExceptT / WriterT- transformer versions of Reader/State/Either/Writer that wrap an inner monad
- MonadPlus- a Monad that also supports failure (mzero) and choice (mplus), underlying `guard`
- Alternative (<|>)- Applicative-level choice operator; picks the first successful alternative
- callCC- "call with current continuation"; captures an escape function for early return
- foldM- monadic left fold that can short-circuit based on intermediate results
When a do-block's type mismatches in a confusing way, mentally desugar it to explicit >>= calls — GHC's inferred types at each bind step are usually far easier to reason about than the compiler's error message on the whole do-block.