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:
-
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".
-
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.
- If you have a function that produces a value in the context, you can use
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
-
Left Identity:
return a >>= f
≡f a
(Putting a value in the context and chaining is the same as just applying the function.) -
Right Identity:
m >>= return
≡m
(Chaining withreturn
doesn't change the result.) -
Associativity:
(m >>= f) >>= g
≡m >>= (\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.)
do
Notation in General
20.5 do
notation is syntactic sugar for chaining monadic operations.
do
with Maybe
Example 1: Using pipeline = do
a <- Just 3
b <- Just 4
return (a + b)
-- Just 7
Equivalent to:
Just 3 >>= (\a ->
Just 4 >>= (\b ->
return (a + b)))
do
with List
Example 2: Using pairs = do
x <- [1,2]
y <- [10,20]
return (x, y)
-- [(1,10),(1,20),(2,10),(2,20)]
do
with IO
Example 3: Using 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 environmentlocal
— 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.
local
to Temporarily Change the Environment
Example 3: Using 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
liftM
24.1 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.
sequence
and sequence_
24.2 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.
sequence_
for IO
Example 3: sequence_ [putStrLn "Hi", putStrLn "There"]
Explanation: Runs actions for their effects, ignores results.
mapM
and mapM_
24.3 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
filterM
24.4 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
foldM
24.5 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)
-
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).
-
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. -
Return A function that takes a regular value and puts it into a monad, so you can use it in monadic code.
-
Monad Laws Three rules that all monads must follow, making sure monads work in predictable ways when you chain actions together.
-
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. -
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.
-
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.
-
ask A function that gets the current environment (the shared setting) inside a Reader monad.
-
local A function that temporarily changes the environment for some part of your code in a Reader monad.
-
Writer Monad Lets you build up a log or notes as your code runs, automatically collecting all the logs along the way.
-
tell Adds something to the log in a Writer monad.
-
State Monad Helps you keep track of a changing value (state) across many steps, without passing it yourself every time.
-
get Fetches the current state inside a State monad.
-
put Sets the state to a new value inside a State monad.
-
modify Changes the current state by applying a function to it inside a State monad.
-
Monad Transformer A tool for combining different monads, so you can work with, say, state and IO together in the same bit of code.
-
lift (or
liftM
) Lets you use a normal function on values inside a monad. -
sequence Takes a bunch of monadic actions and turns them into a single action that gives you all the results together.
-
sequence_ Like
sequence
, but ignores all the results and just does the actions. -
mapM Applies a monadic function to each item in a list, collecting the results in a monad.
-
mapM_ Like
mapM
, but just does the actions for their effects and ignores the results. -
filterM Filters a list using a test that involves monads, so the test can do things like IO or work with state.
-
foldM Like a regular fold (reduce), but lets you do monadic actions as you combine values.
-
Applicative A kind of type that lets you apply functions to values inside a context (like a box). All monads are also applicative.
-
Functor A type that you can use with
fmap
to apply a function to its contents, like mapping over a list. -
Effect Something special your code does besides just returning a value—like printing, changing state, or logging.
-
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!