Monads Can Be Used for Many Things!
- Partial Functions
- Global Variables
- Parsing
- Exceptions
- Test Generation
- Concurrency
- …
Exception Handling
Recall our expressions with division
data Expr
= Number Int -- ^ 0,1,2,3,4
| Plus Expr Expr -- ^ e1 + e2
| Div Expr Expr -- ^ e1 / e2
deriving (Show)We had a potentially crashing evaluator
eval :: Expr -> Int
eval (Number n) = n
eval (Plus e1 e2) = eval e1 + eval e2
eval (Div e1 e2) = eval e1 `div` eval e2
-- >>> eval (Div (Val 10) (Plus (Number 5) (Number (-5))))
-- Exception: Divide by zero
We defined a Result type
made it a Monad
and then we can write
eval :: Expr -> Result Int
eval (Number n) = return n
eval (Plus e1 e2) = do {n1 <- eval e1; n2 <- eval e2; return (n1 + n2) }
eval (Div e1 e2) = do { n1 <- eval e1;
n2 <- eval e2;
if n2 /= 0
then return (n1 `div` n2)
else Err ("DBZ: " ++ show e2)
}which doesn’t crash but returns an Err
and when it succeeds it returns an Ok
Generalizing Result to Either
The standard library generalizes the Result type to Either
Err sbecomesLeft sOk vbecomesRight vResult abecomesEither String a
(But we can data other than String in the Left values)
EXERCISE: Generalizing Result Monad to Either Monad
Lets translate the old Monad instance for Result
instance Monad Result where
-- return :: a -> Result a
return x = Ok x
-- (>>=) :: Result a -> (a -> Result b) -> Result b
(Ok v) >>= f = f v
(Err s) >>= _ = sinto a Monad instance for Either
instance Monad (Either e) where
-- return :: a -> Either e a
return x = ???
-- (>>=) :: Either e a -> (a -> Either e b) -> Either e b
(Right v) >>= f = ???
(Left s) >>= _ = ???
QUIZ
We can rewrite eval to return an Either
eval :: Expr -> Either Expr Int
eval (Number n) = return n
eval (Plus e1 e2) = do n1 <- eval e1
n2 <- eval e2
return (n1+n2)
eval (Div e1 e2) = do n1 <- eval e1
n2 <- eval e2
if n2 /= 0
then return (n1 `div` n2)
else Left e2What does quiz evaluate to?
A. Err "DBZ: Plus (Number 5) (Number (-5))"
B. Left "DBZ: Plus (Number 5) (Number (-5))"
C. Run-time Exception
D. Plus (Number 5) (Number (-5))
E. Left (Plus (Number 5) (Number (-5)))
Either is an Exception Monad!
What can you do with exceptions?
throwErroran exception (with some value) …catchErroran exception (and use its value) …
1. throwing an Exception
We can simply define
and now voila
eval :: Expr -> Either Expr Int
eval (Number n) = return n
eval (Plus e1 e2) = do n1 <- eval e1
n2 <- eval e2
return (n1 + n2)
eval (Div e1 e2) = do n1 <- eval e1
n2 <- eval e2
if n2 /= 0
then return (n1 `div` n2)
else throw e2Exactly the same evaluator
Result is a
Left==> an exception came all the way to the top.Eithermonad ensures the “exception” shoots to the top!
No further evaluation happens after a throw because ???
catching an exception
How to catch an exception?
Lets change our Expr type to
Informally, try e n evaluates to e but
if
eis undefined due to divide-by-zerothen evaluate to
n
eval :: Expr -> Either Expr Int
eval (Number n) = return n
eval (Plus e1 e2) = do n1 <- eval e1
n2 <- eval e2
return (n1+n2)
eval (Div e1 e2) = do n1 <- eval e1
n2 <- eval e2
if n2 /= 0
then return (n1 `div` n2)
else throw e2
eval (Try e n) = catch (eval e) (\_ -> return n)QUIZ
What should the type of catch be?
A. Either e a -> (a -> Either e b) -> Either e b
B. Either e a -> (e -> Either e b) -> Either e b
C. Either e a -> (e -> Either e a) -> Either e a
D. Either e a -> Either e a -> Either e a
E. Either e a -> Either e b -> Either e b
Implementing catch
Lets implement the catch function!
catch :: Either e a -> (e -> Either e a) -> Either e a
catch (Left e) handler = ???
catch (Right a) handler = ???
QUIZ
catch :: Either e a -> (e -> Either e a) -> Either e a
catch (Left e) handle = ???
catch (Right a) handler = ???
eval :: Expr -> Either Expr Int
eval (Number n) = return n
eval (Plus e1 e2) = do n1 <- eval e1
n2 <- eval e2
return (n1+n2)
eval (Div e1 e2) = do n1 <- eval e1
n2 <- eval e2
if n2 /= 0
then return (n1 `div` n2)
else throw e2
eval (Try e n) = catch (eval e) (\_ -> return n)
e1 = Div (Number 10) (Plus (Number 5) (Number (-5)))
e1' = Try e1 7
quiz = eval (Try e1 7)What does quiz evaluate to?
A. Right 7
B. Left 7
C. Right 0
D. Left 0
E. Left (Plus (Number 5) (Number (-5)))
Either is an Exception Monad!
throwan exception (with some value) …catchan exception (and use its value) …
throw :: e -> Either e a
throw e = Left e
catch :: Either e a -> (e -> Either e a) -> Either e a
catch (Left e) handle = handle e
catch (Right e) _ = Right e
Monads Can Be Used for Many Things!
- Partial Functions
- Global State
- Parsing
- Exceptions
- Test Generation
- Concurrency
- …
… but what if I want Exceptions and Global State ?
Mixing Monads
What if I want Exceptions and Global State ?
Profiling with the ST Monad
Lets implement a profiling monad that counts the number of operations
We can write a runProfile that
- executes the transformer from
0 - and renders the result
runProfile :: (Show a) => Profile a -> String
runProfile st = showValCount (runState st 0)
showValCount :: (Show v, Show c) => (v, c) -> String
showValCount (val, count) = "value: " ++ show val ++ ", count: " ++ show countA function to increment the counter
A Profiling Evaluator
We can use count to write a profiling evaluator
evalProf :: Expr -> Profile Int
evalProf = eval
where
eval (Number n) = return n
eval (Plus e1 e2) = do n1 <- eval e1
n2 <- eval e2
count
return (n1+n2)
eval (Div e1 e2) = do n1 <- eval e1
n2 <- eval e2
count
return (n1 `div` n2) And now, as there are two operations, we get
>>> e1
Div (Number 10) (Plus (Number 5) (Number 5))
>>> runProfile (evalProf e1)
"value: 1, count: 2"
But what about Divide-by-Zero?
Bad things happen…
>>> e2
Div (Number 10) (Plus (Number 5) (Number (-5)))
>>> runProfile (evalProf e2)
*** Exception: divide by zero
"value: Problem: How to get global state AND exception handling ?
Mixing Monads with Transformers
Start with a Basic Monad

m implements
- no special operations
Transform it to add some Capabilities

Transform1 m implements
moperations and- operations added by
Transform1
Transform again to add more Capabilities

Transform2 (Transform1 m) implements
moperations and- operations added by
Transform1and - operations added by
Transform2
… And so on

Transform3 (Transform2 (Transform1 m)) implements
moperations and- operations added by
Transform1and - operations added by
Transform2and - operations added by
Transform3…
Reminiscent of the Decorator Design Pattern or Python’s Decorators.
Mixing Monads with Transformers
Step 1: Specifying Monads with Extra Features
Step 2: Implementing Monads with Extra Features
Specifying Monads with Extra Features
First, instead of using concrete monads
- e.g.
ProfileorEither
We will use type-classes to abstractly specify a monad’s capabilities
- e.g.
MonadState s morMonadError e m
A Class for State-Transformers Monads
The class MonadState s m defined in the Control.Monad.State says
mis a State-Transformer monad with state types
That is to say, m implements
>>=andreturnoperations specified byMonadandgetandputoperations specified byMonadState!
Generalize Types to use Classes
So we can generalize the type of count to use MonadState Int m
A Class for Exception Handling Monads
The class MonadError e m defined in [Control.Monad.Except][6] says
mis a Exception-Handling monad with exception typee
That is to say, m implements
>>=andreturnoperations specified byMonadandthrowErrorandcatchErroroperations specified byMonadError!
Generalize Types to use Classes
So we can generalize the type of tryCatch to use MonadError e m
Generalize eval to use Constraints
We can now specify that eval uses a monad m that implements
MonadState IntandMonadError Expr
eval :: (MonadState Int m, MonadError Expr m) => Expr -> m Int
eval (Number n) = return n
eval (Plus e1 e2) = do n1 <- eval e1
n2 <- eval e2
count
return (n1 + n2)
eval (Div e1 e2) = do n1 <- eval e1
n2 <- eval e2
count
if (n2 /= 0)
then return (n1 `div` n2)
else throwError e2
eval (Try e n) = tryCatch (eval e) nLets try to run it!
>>> e1
>>> evalMix e1
... GHC yells "please IMPLEMENT this MAGIC monad that implements BOTH features"
Mixing Monads with Transformers
Step 1: Specifying Monads with Extra Features
Step 2: Implementing Monads with Extra Features
Implementing Monads with Extra Features

Transform2 (Transform1 m) implements
moperations and- operations added by
Transform1and - operations added by
Transform2
We require
- A basic monad
m - A Transform1 that adds
Statecapabilities - A Transform2 that adds
Exceptioncapabilities
A Basic Monad
First, lets make a basic monad
- only implements
>>=andreturn
A very basic monad: just a wrapper (Id) around the value (a)
- No extra features

A Transform that adds State Capabilities
The transformer StateT s m defined in the Control.Monad.State module - takes as input monad m and
- transforms it into a new monad
m'
such that m' implements
all the operations that
mimplementsand adds State-transformer capabilities
StateT s m satisfies the constraint (MonadState s (StateT s m))
A State-transformer over Int states

We can go back and give evalProf the type
A Transform that adds Exception Capabilities
The transformer ExceptT e m
- takes as input a monad
mand - transforms it into a new monad
m'
such that m' implements
all the operations that
mimplementsand adds Exception-handling capabilities
ExceptT e m satisfies the constraint (MonadError e (ExceptT e m))
An Exception Handler Monad with Expr-typed exceptions

We can go back and give evalThrowCatch the type
Composing Transformers
We can use both transformers to get both powers!

ExnProf implements State-transformer-over Int and Exception-handling-over-Expr
EXERCISE: Executing the Combined Transformer
Recall that
Lets write a function
such that
>>> runExnProf (eval e1)
"value: 1, count: 2"
>>> runExnProf (eval e2)
"Plus (Number 5) (Number (-5)) after 2 operations"
TRY AT HOME: Combining in a Different Order
We can also combine the transformers in a different order

ExnProf implements State-transformer-over Int and Exception-handling-over-Expr
Can you implement the function
such that when you are done, we can get the following behavior?
>>> runProfExn (eval e1)
"value: 1, count: 2"
>>> runProfExn (eval e2)
"Left (Plus (Number 5) (Number (-5)))"
Summary: Mixing Monads with Many Features
1. Transformers add capabilities to Monads

Transform2 (Transform1 m) implements
moperations and- operations added by
Transform1and - operations added by
Transform2
2. StateT and ExceptT add State and Exceptions
- Start with a basic monad
Identity - Use
StateT Intto add global-Intstate-update capabilities - Use
ExceptT Exprto add exception-handling capabilities
Play around with this in your homework assignment!