Monads Tutorial English - wimsio/universities GitHub Wiki

Advanced Haskell Monads: In-Depth Tutorials


Table of Contents

20. Monad  20.1 Basic Idea (definition without details)  20.2 Intuitive Examples  20.3 Extracting the Pattern  20.4 Complete Definition (with all details and laws)  20.5 do Notation in General

21. Reader Monad  21.1 Incentive/Motivation  21.2 Binding Strategy  21.3 Definition  21.4 Examples

22. Writer Monad  22.1 Incentive/Motivation  22.2 Binding Strategy  22.3 Definition  22.4 Examples

23. State Monad  23.1 Incentive/Motivation  23.2 Binding Strategy  23.3 Definition  23.4 Examples

24. Monadic Functions / Operating with Monads  24.1 liftM  24.2 sequence and sequence_  24.3 mapM and mapM_  24.4 filterM  24.5 foldM

25. Transformers

Glossary of Terms

Multiple Choice Questions (with Answers)


20. Monad


20.1 Basic Idea (Definition Without Details)

A monad is a design pattern in Haskell that allows you to sequence computations while abstracting away specific concerns such as context, failure, state, or side effects. Monads make it possible to write modular code that handles these concerns in a consistent way. Think of a monad as a "computation builder" that chains steps together.


20.2 Intuitive Examples

Let's look at three monads in action: Maybe, [] (list), and IO.

Example 1: Maybe Monad – Safe Division

Suppose you want to divide two numbers, but handle the possibility of dividing by zero:

safeDiv :: Double -> Double -> Maybe Double
safeDiv _ 0 = Nothing
safeDiv x y = Just (x / y)

Usage:

result1 = safeDiv 10 2    -- Just 5.0
result2 = safeDiv 10 0    -- Nothing

Explanation: Maybe is a context that can represent a value or "no value" (Nothing). This lets us safely signal errors without exceptions.


Example 2: List Monad – Non-Determinism

Suppose you want all possible pairings of two sets:

pairs :: [Int] -> [Int] -> [(Int, Int)]
pairs xs ys = do
  x <- xs
  y <- ys
  return (x, y)

Usage:

result = pairs [1,2] [3,4] -- [(1,3),(1,4),(2,3),(2,4)]

Explanation: Each combination is generated automatically. The list monad chains computations for each element.


Example 3: IO Monad – User Input and Output

main :: IO ()
main = do
  putStrLn "Enter your name:"
  name <- getLine
  putStrLn ("Hello, " ++ name ++ "!")

Explanation: IO is a context that models side effects. Chaining operations with do ensures actions are performed in order.


20.3 Extracting the Pattern

Let’s see what these have in common:

  1. They wrap or "tag" a value:

    • Maybe a means "an a, or nothing".
    • [a] means "zero or more a’s".
    • IO a means "an a, produced by an action".
  2. They provide a way to chain computations:

    • If you have a function that produces a value in the context, you can use >>= to chain more steps, always respecting the context.

Example 1: Chaining Maybes

sqrtMaybe :: Double -> Maybe Double
sqrtMaybe x
  | x < 0     = Nothing
  | otherwise = Just (sqrt x)

pipeline = do
  a <- safeDiv 10 2    -- Just 5
  b <- sqrtMaybe a     -- Just 2.236...
  return b
-- pipeline == Just 2.236...

Example 2: Chaining Lists

numbers = [1,2]
adders  = [10,100]

result = numbers >>= (\n -> adders >>= (\a -> return (n + a)))
-- [11,101,12,102]

Example 3: Chaining IO

main :: IO ()
main = getLine >>= (\name -> putStrLn ("Hi, " ++ name ++ "!"))

20.4 Complete Definition (with All Details/Laws)

Monad Typeclass

class Applicative m => Monad m where
    (>>=)  :: m a -> (a -> m b) -> m b
    return :: a -> m a
  • >>= is "bind": takes a value in a context (m a) and a function that takes a normal value and returns a value in the same context (a -> m b).
  • return lifts a plain value into the context.

Monad Laws

  1. Left Identity: return a >>= ff a (Putting a value in the context and chaining is the same as just applying the function.)

  2. Right Identity: m >>= returnm (Chaining with return doesn't change the result.)

  3. Associativity: (m >>= f) >>= gm >>= (\x -> f x >>= g) (Grouping doesn't affect the result.)


Example 1: Law Verification with Maybe

f x = Just (x + 1)
m = Just 2

-- Left identity
return 2 >>= f  -- Just 3
f 2             -- Just 3

-- Right identity
m >>= return    -- Just 2
m               -- Just 2

-- Associativity
((m >>= f) >>= f) == (m >>= (\x -> f x >>= f))

Example 2: Law Verification with List

g x = [x, x*2]

-- Left identity
return 3 >>= g   -- [3,6]
g 3              -- [3,6]

Example 3: Law Verification with IO

main = do
  putStrLn "Enter:"
  n <- readLn
  print (n * 2)

(You can imagine that reading input and printing output must respect these laws to compose cleanly.)


20.5 do Notation in General

do notation is syntactic sugar for chaining monadic operations.

Example 1: Using do with Maybe

pipeline = do
  a <- Just 3
  b <- Just 4
  return (a + b)
-- Just 7

Equivalent to:

Just 3 >>= (\a ->
  Just 4 >>= (\b ->
    return (a + b)))

Example 2: Using do with List

pairs = do
  x <- [1,2]
  y <- [10,20]
  return (x, y)
-- [(1,10),(1,20),(2,10),(2,20)]

Example 3: Using do with IO

main = do
  putStrLn "What's your name?"
  name <- getLine
  putStrLn ("Hello, " ++ name)

21. Reader Monad


21.1 Incentive/Motivation

Imagine needing to pass configuration (e.g., database connection, API key, environment) to many functions. Passing it manually is error-prone and cluttered. The Reader monad hides this boilerplate, providing implicit access to shared, read-only data.


21.2 Binding Strategy

Instead of passing the environment explicitly, each function reads from the environment when needed. Chaining with Reader ensures the environment is available throughout.


21.3 Definition

newtype Reader r a = Reader { runReader :: r -> a }

Key functions:

  • ask — retrieve the environment
  • local — modify the environment for a subcomputation

The Monad instance:

instance Monad (Reader r) where
  return a = Reader $ \_ -> a
  Reader ra >>= f = Reader $ \r -> runReader (f (ra r)) r

21.4 Examples

Example 1: Simple Configuration Lookup

import Control.Monad.Reader

showConfig :: Reader String String
showConfig = do
  config <- ask
  return ("The config is: " ++ config)

-- runReader showConfig "debug"
-- "The config is: debug"

Explanation: The environment "debug" is automatically available to all computations in the Reader.


Example 2: Nested Use – Environment Propagation

import Control.Monad.Reader

greet :: Reader String String
greet = do
  name <- ask
  return ("Hi, " ++ name)

loudGreet :: Reader String String
loudGreet = do
  g <- greet
  return (map toUpper g)

-- runReader loudGreet "alice"  -- "HI, ALICE"

Explanation: The environment is automatically passed through the chain.


Example 3: Using local to Temporarily Change the Environment

import Control.Monad.Reader

addPrefix :: Reader String String
addPrefix = local ("Mr. " ++) greet

-- runReader addPrefix "Bond"
-- "Hi, Mr. Bond"

Explanation: local temporarily modifies the environment for the inner computation.


22. Writer Monad


22.1 Incentive/Motivation

You often want to accumulate logs or auxiliary output (like steps taken, warnings, debug info) while performing a computation. The Writer monad provides a principled way to do this, automatically collecting output alongside the result.


22.2 Binding Strategy

Each computation produces a value and some log. Chaining operations means combining their logs (using a Monoid like lists for concatenation, numbers for sums, etc.).


22.3 Definition

import Control.Monad.Writer

type WriterLog = Writer [String]

The Monad instance ensures logs are concatenated as you chain computations.


22.4 Examples

Example 1: Simple Logging

import Control.Monad.Writer

addLog :: Int -> Writer [String] Int
addLog x = writer (x, ["Got: " ++ show x])

computation = do
  a <- addLog 2
  b <- addLog 3
  return (a + b)

-- runWriter computation
-- (5,["Got: 2","Got: 3"])

Example 2: Logging Steps in Recursion

import Control.Monad.Writer

factorial :: Int -> Writer [String] Int
factorial 0 = do
  tell ["0! = 1"]
  return 1
factorial n = do
  m <- factorial (n - 1)
  let res = n * m
  tell [show n ++ "! = " ++ show res]
  return res

-- runWriter (factorial 3)
-- (6,["0! = 1","1! = 1","2! = 2","3! = 6"])

Example 3: Counting Operations with Sum Monoid

import Control.Monad.Writer
import Data.Monoid

countOps :: [Int] -> Writer (Sum Int) Int
countOps [] = return 0
countOps (x:xs) = do
  tell (Sum 1)
  rest <- countOps xs
  return (x + rest)

-- runWriter (countOps [1,2,3])
-- (6,Sum {getSum = 3})

23. State Monad


23.1 Incentive/Motivation

When you need to maintain and update state as computations progress (counters, random numbers, game state), the State monad lets you thread state implicitly, avoiding explicit passing and updating.


23.2 Binding Strategy

Each step is a function from a state to a (result, new state) pair. The State monad automatically passes the state along the chain.


23.3 Definition

import Control.Monad.State

type Counter = State Int

The Monad instance manages state threading.


23.4 Examples

Example 1: Simple Counter

import Control.Monad.State

tick :: State Int Int
tick = do
  n <- get
  put (n + 1)
  return n

-- runState tick 0       -- (0,1)
-- runState (tick >> tick) 0 -- (1,2)

Example 2: Counting Elements in a List

import Control.Monad.State

countElems :: [a] -> State Int ()
countElems [] = return ()
countElems (_:xs) = do
  modify (+1)
  countElems xs

-- execState (countElems [1,2,3,4]) 0  -- 4

Example 3: Stateful Accumulation

import Control.Monad.State

sumElems :: [Int] -> State Int ()
sumElems [] = return ()
sumElems (x:xs) = do
  modify (+x)
  sumElems xs

-- execState (sumElems [1,2,3]) 0 -- 6

24. Monadic Functions / Operating with Monads


24.1 liftM

Lifts a pure function to operate on monadic values.

Example 1: Maybe

import Control.Monad (liftM)
liftM (+1) (Just 2) -- Just 3

Example 2: List

liftM (*2) [1,2,3]  -- [2,4,6]

Example 3: IO

import Control.Monad (liftM)
main = do
  input <- liftM reverse getLine
  putStrLn input

Explanation: Reads a line and prints it reversed.


24.2 sequence and sequence_

Example 1: List of Maybe

sequence [Just 1, Just 2, Just 3]  -- Just [1,2,3]
sequence [Just 1, Nothing, Just 3] -- Nothing

Example 2: List of IO

sequence [getLine, getLine]

Explanation: Reads two lines and returns them as a list.

Example 3: sequence_ for IO

sequence_ [putStrLn "Hi", putStrLn "There"]

Explanation: Runs actions for their effects, ignores results.


24.3 mapM and mapM_

Example 1: Printing a List

mapM_ print [1,2,3]
-- Prints: 1\n2\n3

Example 2: Reading N Lines

main = do
  names <- mapM (\_ -> getLine) [1..3]
  print names

Example 3: With Maybe

mapM (\x -> if x > 0 then Just x else Nothing) [1,2,3] -- Just [1,2,3]
mapM (\x -> if x > 0 then Just x else Nothing) [1,0,3] -- Nothing

24.4 filterM

Example 1: Filter with IO

import Control.Monad

main = do
  result <- filterM (\x -> do
    putStrLn ("Keep " ++ show x ++ "? (y/n)")
    resp <- getLine
    return (resp == "y")) [1,2,3]
  print result

Example 2: Filter with List

filterM (\x -> [True, False]) [1,2]
-- [1,2],[1],[2],[](/wimsio/universities/wiki/1,2],[1],[2],[)

Example 3: Filter with Maybe

filterM (\x -> if x < 5 then Just True else Nothing) [1,2,3] -- Just [1,2,3]
filterM (\x -> if x < 5 then Just True else Nothing) [1,6,3] -- Nothing

24.5 foldM

Example 1: Safe Division

import Control.Monad

safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)

foldM safeDiv 100 [2,5]   -- Just 10
foldM safeDiv 100 [2,0,5] -- Nothing

Example 2: Accumulate with Logging

import Control.Monad.Writer

addLog :: Int -> Int -> Writer [String] Int
addLog acc x = writer (acc + x, ["Added " ++ show x])

result = runWriter $ foldM addLog 0 [1,2,3]
-- (6, ["

Absolutely! Here’s the numbered glossary, but this time explained in simple, beginner-friendly language. Each entry is direct and uses everyday programming analogies.


Glossary of Key Concepts (Simple Beginner Explanations)

  1. Monad A way to organize your code so you can chain together steps, especially when each step might do something special (like maybe fail, or do IO, or change some state).

  2. Bind (>>=) An operator that lets you connect one step to the next in a monad. It takes the result from one action and gives it to the next.

  3. Return A function that takes a regular value and puts it into a monad, so you can use it in monadic code.

  4. Monad Laws Three rules that all monads must follow, making sure monads work in predictable ways when you chain actions together.

  5. Context The "situation" or "setting" your value is in. For example, Maybe means "it could be missing," IO means "it might involve input/output," and so on.

  6. do Notation Special Haskell syntax that lets you write monad code as if you were writing steps one after another, making it look like regular imperative code.

  7. Reader Monad A tool for when you want a lot of functions to have access to the same configuration or environment, without passing it to each function yourself.

  8. ask A function that gets the current environment (the shared setting) inside a Reader monad.

  9. local A function that temporarily changes the environment for some part of your code in a Reader monad.

  10. Writer Monad Lets you build up a log or notes as your code runs, automatically collecting all the logs along the way.

  11. tell Adds something to the log in a Writer monad.

  12. State Monad Helps you keep track of a changing value (state) across many steps, without passing it yourself every time.

  13. get Fetches the current state inside a State monad.

  14. put Sets the state to a new value inside a State monad.

  15. modify Changes the current state by applying a function to it inside a State monad.

  16. Monad Transformer A tool for combining different monads, so you can work with, say, state and IO together in the same bit of code.

  17. lift (or liftM) Lets you use a normal function on values inside a monad.

  18. sequence Takes a bunch of monadic actions and turns them into a single action that gives you all the results together.

  19. sequence_ Like sequence, but ignores all the results and just does the actions.

  20. mapM Applies a monadic function to each item in a list, collecting the results in a monad.

  21. mapM_ Like mapM, but just does the actions for their effects and ignores the results.

  22. filterM Filters a list using a test that involves monads, so the test can do things like IO or work with state.

  23. foldM Like a regular fold (reduce), but lets you do monadic actions as you combine values.

  24. Applicative A kind of type that lets you apply functions to values inside a context (like a box). All monads are also applicative.

  25. Functor A type that you can use with fmap to apply a function to its contents, like mapping over a list.

  26. Effect Something special your code does besides just returning a value—like printing, changing state, or logging.

  27. Pure Function A function that always gives the same answer for the same input and doesn’t do anything else (like printing or changing variables).


If you want any entries even simpler or want examples added for each, just let me know!


Multiple Choice Questions


1. Which of the following is the correct type signature for the Monad bind operator (>>=)?

A. m a -> m b -> m a B. m a -> (a -> m b) -> m b C. (a -> m b) -> m a -> m b D. a -> m a -> m b

Answer: B (It chains a monadic value to a function producing a monadic value.)


2. What is the result of the following?

Just 4 >>= (\x -> Just (x * 2))

A. Nothing B. Just 4 C. Just 8 D. [8]

Answer: C (The function doubles the value 4 inside the Maybe.)


3. Which law does m >>= return == m describe for monads?

A. Left Identity B. Associativity C. Right Identity D. Functor Law

Answer: C (Right Identity: binding with return is a no-op.)


4. Which monad is best suited for logging as a computation proceeds?

A. Maybe B. State C. Writer D. Reader

Answer: C (Writer accumulates logs or outputs alongside computation.)


5. What does ask do in the Reader monad?

A. Changes the environment B. Returns the current environment C. Writes to the log D. Gets the current state

Answer: B (ask retrieves the current environment value.)


6. What is the main purpose of the State monad?

A. Handling exceptions B. Accumulating log messages C. Threading state through computations D. Providing a read-only environment

Answer: C (State monad passes state implicitly between functions.)


7. Given sequence [Just 1, Just 2, Just 3], what is the result?

A. [Just 1, Just 2, Just 3] B. Just [1,2,3] C. Nothing D. [1,2,3]

Answer: B (sequence “flips” the list of Maybes to Maybe of list.)


8. What is the result of liftM (+1) (Just 5)?

A. 6 B. Just 6 C. [6] D. Nothing

Answer: B (liftM applies (+1) to the value inside Just.)


9. Which of the following is NOT a use-case for monad transformers?

A. Combining Reader and Writer effects B. Adding logging to an IO computation C. Passing an environment in a pure computation D. Handling errors in stateful computations

Answer: C (Monad transformers are about combining effects, not pure code.)


10. Which function would you use to apply a monadic predicate to filter a list?

A. mapM B. filterM C. foldM D. sequence_

Answer: B (filterM is the monadic version of filter.)


11. What does the following code output?

runReader (local (map toUpper) ask) "hello"

A. "HELLO" B. "hello" C. ["HELLO"] D. Nothing

Answer: A (local temporarily changes the environment to uppercase.)


12. Which of the following does tell do in the Writer monad?

A. Retrieves the current log B. Modifies the environment C. Appends a value to the log D. Sets the state

Answer: C (tell appends to the Writer’s log.)


13. What is the output of this program?

runState (replicateM 3 tick) 0
-- where tick = do { n <- get; put (n + 1); return n }

A. [0,1,2], 3 B. [1,2,3], 3 C. [0,1,2], 2 D. [0,1,2], 1

Answer: A (Each call returns the current state before increment; after 3 ticks, state is 3.)


14. What does the function foldM do?

A. Folds a list with a pure function B. Folds a list using a monadic function C. Maps a monadic function over a list D. Folds a list in reverse

Answer: B (foldM folds a list left-to-right using a monadic function.)


15. In the list monad, what does this code produce?

do x <- [1,2]
   y <- [10,20]
   return (x + y)

A. [11,12,21,22] B. [11,21,12,22] C. [11,21,12,22,13,23] D. [11,12,13,14]

Answer: B (All combinations: 1+10=11, 1+20=21, 2+10=12, 2+20=22.)


16. Which monad is most suitable for providing a context-dependent computation with read-only access to a configuration?

A. Maybe B. Writer C. Reader D. State

Answer: C (Reader monad gives functions access to a shared environment.)


17. What is the output of this program?

runWriter $ do
  tell ["Start"]
  tell ["Middle"]
  tell ["End"]

A. ([],["Start","Middle","End"]) B. ([“Start”,”Middle”,”End”], []) C. ([()],[“Start”,”Middle”,”End”]) D. ((),["Start","Middle","End"])

Answer: D (The result is (), the log is the list of strings.)


18. If you have StateT Int IO (), which of these do you gain?

A. Only state B. Only IO C. Both state and IO D. Only Writer

Answer: C (You get both state threading and IO effects.)


19. What will the following code print?

main = mapM_ print [1,2,3]

A. [1,2,3] B. 1 2 3 (each on its own line) C. Just [1,2,3] D. Nothing

Answer: B (Each number is printed on a separate line.)


20. Which monadic function ignores the results and only performs the actions for their effects?

A. mapM B. foldM C. mapM_ D. sequence

Answer: C (mapM_ is used when you want to perform actions just for their effects.)


Let me know if you’d like explanations, more challenge questions, or a printable quiz version!