The rest of the quarter: Grading and evaluation
- PA4 + 5 due next Tuesday
- Will drop lowest PA
- Work with whoever you want
- PA5: only BST.hs (85pts max)
- Final next Wednesday
- On everything we covered pre-strike
- I will respect TAs withholding labor. If you need grades and they aren’t available, let me know and I’ll make it happen.
Questions/comments/concerns?
The rest of the quarter: Content
- Today: review via introduction to functors and exception handling
- Wednesday: monads, briefly
- Friday: review (come prepared with questions)
- There are practice finals on the website now: do them and ask questions!
Suggestions? What do you want to get out of this week?
I will hold office hours one more time this week, and again on Monday
The Mantra
Don’t Repeat Yourself
In this lecture we will see advanced ways to abstract code patterns
Outline
- Functors
- Monads
- Writing apps with monads
Recall: HOF
Recall how we used higher-order functions to abstract code patters
Iterating Through Lists
data List a
= []
| (:) a (List a)
Rendering the Values of a List
-- >>> showList [1, 2, 3]
-- ["1", "2", "3"]
showList :: [Int] -> [String]
showList [] = []
showList (n:ns) = show n : showList ns
Squaring the values of a list
-- >>> sqrList [1, 2, 3]
-- 1, 4, 9
sqrList :: [Int] -> [Int]
sqrList [] = []
sqrList (n:ns) = n^2 : sqrList ns
Common Pattern: map
over a list
Refactor iteration into mapList
mapList :: (a -> b) -> [a] -> [b]
mapList f [] = []
mapList f (x:xs) = f x : mapList f xs
Reuse mapList
to implement showList
and sqrList
showList xs = mapList (\n -> show n) xs
sqrList xs = mapList (\n -> n ^ 2) xs
Iterating Through Trees
data Tree a
= Leaf
| Node a (Tree a) (Tree a)
Rendering the Values of a Tree
-- >>> showTree (Node 2 (Node 1 Leaf Leaf) (Node 3 Leaf Leaf))
-- (Node "2" (Node "1" Leaf Leaf) (Node "3" Leaf Leaf))
showTree :: Tree Int -> Tree String
showTree Leaf = ???
showTree (Node v l r) = ???
Squaring the values of a Tree
-- >>> sqrTree (Node 2 (Node 1 Leaf Leaf) (Node 3 Leaf Leaf))
-- (Node 4 (Node 1 Leaf Leaf) (Node 9 Leaf Leaf))
sqrTree :: Tree Int -> Tree Int
sqrTree Leaf = ???
sqrTree (Node v l r) = ???
QUIZ: Mapping over a Tree
If we refactor iteration into mapTree
, what should its type be?
mapTree :: ???
showTree t = mapTree (\n -> show n) t
sqrTree t = mapTree (\n -> n ^ 2) t
(A) (Int -> Int) -> Tree Int -> Tree Int
(B) (Int -> String) -> Tree Int -> Tree String
(C) (Int -> a) -> Tree Int -> Tree a
(D) (a -> a) -> Tree a -> Tree a
(E) (a -> b) -> Tree a -> Tree b
Mapping over Trees
Let’s write mapTree
:
mapTree :: (a -> b) -> Tree a -> Tree b
mapTree f Leaf = ???
mapTree f (Node v l r) = ???
Abstracting across Datatypes
Wait, this looks familiar…
type List a = [a]
mapList :: (a -> b) -> List a -> List b -- List
mapTree :: (a -> b) -> Tree a -> Tree b -- Tree
Can we provide a generic map
function that works for List
, Tree
, and other datatypes?
A Class for Mapping
Not all datatypes support mapping over, only some of them do.
So let’s make a typeclass for it!
class Functor t where
fmap :: ???
QUIZ
What type should we give to fmap
?
class Functor t where
fmap :: ???
(A) (a -> b) -> t -> t
(B) (a -> b) -> [a] -> [b]
(C) (a -> b) -> t a -> t b
(D) (a -> b) -> Tree a -> Tree b
(E) None of the above
Reuse Iteration Across Types
instance Functor [] where
fmap = mapList
instance Functor Tree where
fmap = mapTree
And now we can do
-- >>> fmap show [1,2,3]
-- ["1", "2", "3"]
-- >>> fmap (^2) (Node 2 (Node 1 Leaf Leaf) (Node 3 Leaf Leaf))
-- (Node 4 (Node 1 Leaf Leaf) (Node 9 Leaf Leaf))
Exercise
Write a Functor
instance for Result
:
data Result a
= Error String
| Ok a
instance Functor Result where
fmap f (Error msg) = ???
fmap f (Ok val) = ???
When you’re done you should see
-- >>> fmap (^ 2) (Ok 3)
Ok 9
-- >>> fmap (^ 2) (Error "oh no")
Error "oh no"
Outline
- Functors [done]
Monads for
2.1. Error Handling
2.2. Mutable State
Writing apps with monads
Next: A Class for Sequencing
Consider a simplified Expr
datatype
data Expr
= Num Int
| Plus Expr Expr
| Div Expr Expr
deriving (Show)
eval :: Expr -> Int
eval (Num n) = n
eval (Plus e1 e2) = eval e1 + eval e2
eval (Div e1 e2) = eval e1 `div` eval e2
-- >>> eval (Div (Num 6) (Num 2))
-- 3
But what is the result of
-- >>> eval (Div (Num 6) (Num 0))
-- *** Exception: divide by zero
My interpreter crashes!
- What if I’m implementing GHCi?
- I don’t want GHCi to crash every time you enter
div 5 0
- I want it to process the error and move on with its life
How can we achieve this behavior?
Error Handling: Take One
Let’s introduce a new type for evaluation results:
data Result a
= Error String
| Value a
Our eval
will now return Result Int
instead of Int
- If a sub-expression had a divide by zero, return
Error "..."
- If all sub-expressions were safe, then return the actual
Value v
eval :: Expr -> Result Int
eval (Num n) = ???
eval (Plus e1 e2) = ???
eval (Div e1 e2) = ???
eval :: Expr -> Result Int
eval (Num n) = Value n
eval (Plus e1 e2) =
case eval e1 of
Error err1 -> Error err1
Value v1 -> case eval e2 of
Error err2 -> Error err2
Value v1 -> Value (v1 + v2)
eval (Div e1 e2) =
case eval e1 of
Error err1 -> Error err1
Value v1 -> case eval e2 of
Error err2 -> Error err2
Value v2 -> if v2 == 0
then Error ("DBZ: " ++ show e2)
else Value (v1 `div` v2)
The good news: interpreter doesn’t crash, just returns Error msg
:
λ> eval (Div (Num 6) (Num 2))
Value 3
λ> eval (Div (Num 6) (Num 0))
Error "DBZ: Num 0"
λ> eval (Div (Num 6) (Plus (Num 2) (Num (-2))))
Error "DBZ: Plus (Num 2) (Num (-2))"
The bad news: the code is super duper gross
Lets spot a Pattern
The code is gross because we have these cascading blocks
case eval e1 of
Error err1 -> Error err1
Value v1 -> case eval e2 of
Error err2 -> Error err2
Value v2 -> Value (v1 + v2)
But these blocks have a common pattern:
- First do
eval e
and get resultres
- If
res
is anError
, just return that error - If
res
is aValue v
then do further processing onv
case res of
Error err -> Error err
Value v -> process v -- do more stuff with v
Lets bottle that common structure in a function >>=
(pronounced bind):
(>>=) :: Result a -> (a -> Result b) -> Result b
(Error err) >>= _ = Error err
(Value v) >>= process = process v
Notice the >>=
takes two inputs:
Result a
: result of the first evaluationa -> Result b
: in case the first evaluation produced a value, what to do next with that value
QUIZ: Bind 1
With >>=
defined as before:
(>>=) :: Result a -> (a -> Result b) -> Result b
(Error msg) >>= _ = Error msg
(Value v) >>= process = process v
What does the following evaluate to?
λ> eval (Num 5) >>= \v -> Value (v + 1)
(A) Type Error
(B) 5
(C) Value 5
(D) Value 6
(E) Error msg
QUIZ: Bind 2
With >>=
defined as before:
(>>=) :: Result a -> (a -> Result b) -> Result b
(Error msg) >>= _ = Error msg
(Value v) >>= process = process v
What does the following evaluate to?
λ> Error "nope" >>= \v -> Value (v + 1)
(A) Type Error
(B) 5
(C) Value 5
(D) Value 6
(E) Error "nope"
A Cleaned up Evaluator
The magic bottle lets us clean up our eval
:
eval :: Expr -> Result Int
eval (Num n) = Value n
eval (Plus e1 e2) = eval e1 >>= \v1 ->
eval e2 >>= \v2 ->
Value (v1 + v2)
eval (Div e1 e2) = eval e1 >>= \v1 ->
eval e2 >>= \v2 ->
if v2 == 0
then Error ("DBZ: " ++ show e2)
else Value (v1 `div` v2)
The gross pattern matching is all hidden inside >>=
!
NOTE: It is crucial that you understand what the code above is doing, and why it is actually just a “shorter” version of the (gross) nested-case-of eval
.
A Class for bind
Like fmap
or show
or ==
, the >>=
operator turns out to be useful across many types (not just Result
)
Let’s create a typeclass for it!
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b -- bind
return :: a -> m a -- return
return
tells you how to wrap an a
value in the monad
- Useful for writing code that works across multiple monads
Monad instance for Result
Let’s make Result
an instance of Monad
!
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
instance Monad Result where
(>>=) :: Result a -> (a -> Result b) -> Result b
(Error msg) >>= _ = Error msg
(Value v) >>= process = process v
return :: a -> Result a
return v = ??? -- How do we make a `Result a` from an `a`?
instance Monad Result where
(>>=) :: Result a -> (a -> Result b) -> Result b
(Error msg) >>= _ = Error msg
(Value v) >>= process = process v
return :: a -> Result a
return v = Value v
Syntactic Sugar
In fact >>=
is so useful there is special syntax for it!
- It’s called the
do
notation
Instead of writing
e1 >>= \v1 ->
e2 >>= \v2 ->
e3 >>= \v3 ->
e
you can write
do v1 <- e1
v2 <- e2
v3 <- e3
e
Thus, we can further simplify our eval
to:
eval :: Expr -> Result Int
eval (Num n) = return n
eval (Plus e1 e2) = do v1 <- eval e1
v2 <- eval e2
return (v1 + v2)
eval (Div e1 e2) = do v1 <- eval e1
v2 <- eval e2
if v2 == 0
then Error ("DBZ: " ++ show e2)
else return (v1 `div` v2)
The Either Monad
Error handling is a very common task!
Instead of defining your own type Result
, you can use Either
from the Haskell standard library:
data Either a b =
Left a -- something has gone wrong
| Right b -- everything has gone RIGHT
Either
is already an instance of Monad
, so no need to define your own >>=
!
Now we can simply define
type Result a = Either String a
and the eval
above will just work out of the box!
Outline
- Functors [done]
Monads for
2.1. Error Handling [done]
2.2. Mutable State
Writing apps with monads
Expressions with a Counter
Consider implementing expressions with a counter:
data Expr
= Num Int
| Plus Expr Expr
| Next -- counter value
deriving (Show)
Behavior we want:
eval
is given the initial counter value- every time we evaluate
Next
(within the call toeval
), the value of the counter increases:
-- 0
λ> eval 0 Next
0
-- 0 1
λ> eval 0 (Plus Next Next)
1
-- 0 1 2
λ> eval 0 (Plus Next (Plus Next Next))
3
How should we implement eval
?
eval (Num n) cnt = ???
eval Next cnt = ???
eval (Plus e1 e2) cnt = ???
QUIZ: State: Take 1
If we implement eval
like this:
eval (Num n) cnt = n
eval Next cnt = cnt
eval (Plus e1 e2) cnt = eval e1 cnt + eval e2 cnt
What would be the result of the following?
λ> eval (Plus Next Next) 0
(A) Type error
(B) 0
(C) 1
(D) 2
It’s going to be 0
because we never increment the counter!
- We need to increment it every time we do
eval Next
- So
eval
needs to return the new counter
Evaluating Expressions with Counter
type Cnt = Int
eval :: Expr -> Cnt -> (Cnt, Int)
eval (Num n) cnt = (cnt, n)
eval Next cnt = (cnt + 1, cnt)
eval (Plus e1 e2) cnt = let (cnt1, v1) = eval e1 cnt
in
let (cnt2, v2) = eval e2 cnt1
in
(cnt2, v1 + v2)
topEval :: Expr -> Int
topEval e = snd (eval e 0)
The good news: we get the right result:
λ> topEval (Plus Next Next)
1
λ> topEval (Plus Next (Plus Next Next))
3
The bad news: the code is super duper gross.
The Plus
case has to “thread” the counter through the recursive calls:
let (cnt1, v1) = eval e1 cnt
in
let (cnt2, v2) = eval e2 cnt1
in
(cnt2, v1 + v2)
- Easy to make a mistake, e.g. pass
cnt
instead ofcnt1
into the secondeval
! - The logic of addition is obscured by all the counter-passing
So unfair, since Plus
doesn’t even care about the counter!
Is it too much to ask that eval
looks like this?
eval (Num n) = return n
eval (Plus e1 e2) = do v1 <- eval e1
v2 <- eval e2
return (v1 + v2)
...
- Cases that don’t care about the counter (
Num
,Plus
), don’t even have to mention it! - The counter is somehow threaded through automatically behind the scenes
- Looks just like in the error handing evaluator
Lets spot a Pattern
let (cnt1, v1) = eval e1 cnt
in
let (cnt2, v2) = eval e2 cnt1
in
(cnt2, v1 + v2)
These blocks have a common pattern:
- Perform first step (
eval e
) using initial countercnt
- Get a result
(cnt', v)
- Then do further processing on
v
using the new countercnt'
let (cnt', v) = step cnt
in process v cnt' -- do things with v and cnt'
Can we bottle this common pattern as a >>=
?
(>>=) step process cnt = let (cnt', v) = step cnt
in process v cnt'
But what is the type of this >>=
?
(>>=) :: (Cnt -> (Cnt, a))
-> (a -> Cnt -> (Cnt, b))
-> Cnt
-> (Cnt, b)
(>>=) step process cnt = let (cnt', v) = step cnt
in process v cnt'
Wait, but this type signature looks nothing like the Monad
’s bind!
(>>=) :: m a -> (a -> m b) -> m b
… or does it???
QUIZ: Type of bind for Counting
What should I replace m t
with to make the general type of monadic bind:
(>>=) :: m a -> (a -> m b) -> m b
look like the type of bind we just defined:
(>>=) :: (Cnt -> (Cnt, a))
-> (a -> Cnt -> (Cnt, b))
-> Cnt
-> (Cnt, b)
(A) It’s impossible
(B) m t = Result t
(C) m t = (Cnt, t)
(D) m t = Cnt -> (Cnt , t)
(E) m t = t -> (Cnt , t)
type Counting a = Cnt -> (Cnt, a)
(>>=) :: Counting a
-> (a -> Counting b)
-> Counting b
(>>=) step process = \cnt -> let (cnt', v) = step cnt
in process v cnt'
Mind blown.
QUIZ: Return for Counting
How should we define return
for Counting
?
type Counting a = Cnt -> (Cnt, a)
-- | Represent value x as a counting computation,
-- don't actually touch the counter
return :: a -> Counting a
return x = ???
(A) x
(B) (0, x)
(C) \c -> (0, x)
(D) \c -> (c, x)
(E) \c -> (c + 1, x)
Cleaned-up evaluator
eval :: Expr -> Counting Int
eval (Num n) = return n
eval (Plus e1 e2) = eval e1 >>= \v1 ->
eval e2 >>= \v2 ->
return (v1 + v2)
eval Next = \cnt -> (cnt + 1, cnt)
Hooray! We rid the poor Num
and Plus
from the pesky counters!
The Next
case has to deal with counters
- but can we somehow hide the representation of
Counting a
? - and make it look more like we just have mutable state that we can
get
andput
? - i.e. write:
eval Next = get >>= \c ->
put (c + 1) >>= \_ ->
return c
How should we define these?
-- | Computation whose return value is the current counter value
get :: Counting Cnt
get = ???
-- | Computation that updates the counter value to `newCnt`
put :: Cnt -> Counting ()
put newCnt = ???
-- | Computation whose return value is the current counter value
get :: Counting Cnt
get = \cnt -> (cnt, cnt)
-- | Computation that updates the counter value to `newCnt`
put :: Cnt -> Counting ()
put newCnt = \_ -> (newCnt, ())
Monad instance for Counting
Let’s make Counting
an instance of Monad
!
- To do that, we need to make it a new datatype
data Counting a = C (Cnt -> (Cnt, a))
instance Monad Counting where
(>>=) :: Counting a -> (a -> Counting b) -> Counting b
(>>=) (C step) process = C final
where
final cnt = let
(cnt', v) = step cnt
C nextStep = process v
in nextStep cnt'
return :: a -> Result a
return v = C (\cnt -> (cnt, v))
We also need to update get
and put
slightly:
-- | Computation whose return value is the current counter value
get :: Counting Cnt
get = C (\cnt -> (cnt, cnt))
-- | Computation that updates the counter value to `newCnt`
put :: Cnt -> Counting ()
put newCnt = C (\_ -> (newCnt, ()))
Cleaned-up Evaluator
Now we can use the do
notation!
eval :: Expr -> Counting Int
eval (Num n) = return n
eval (Plus e1 e2) = do v1 <- eval e1
v2 <- eval e2
return (v1 + v2)
eval Next = do
cnt <- get
_ <- put (cnt + 1)
return (cnt)
The State Monad
Threading state is a very common task!
Instead of defining your own type Counting a
, you can use State s a
from the Haskell standard library:
data State s a = State (s -> (s, a))
State
is already an instance of Monad
, so no need to define your own >>=
!
Now we can simply define
type Counting a = State Cnt a
and the eval
above will just work out of the box!
Outline
- Functors [done]
Monads for
2.1. Error Handling [done]
2.2. Mutable State [done]
Writing apps with monads
Writing Applications
In most programming classes, they start with a “Hello world!” program.
In 130, we will end with it.
Why is it hard to write a program that prints “Hello world!” in Haskell?
Haskell is pure
Haskell programs don’t do things!
A program is an expression that evaluates to a value (and nothing else happens)
A function of type
Int -> Int
computes a single integer output from a single integer input and does nothing elseMoreover, it always returns the same output given the same input (referential transparency)
Specifically, evaluation must not have any side effects
change a global variable or
print to screen or
read a file or
send an email or
launch a missile.
But… how to write “Hello, world!”
But, we want to …
- print to screen
- read a file
- send an email
A language that only lets you write factorial
and fibonacci
is … not very useful!
Thankfully, you can do all the above via a very clever idea: Recipe
Recipes
This analogy is due to Joachim Brietner
Haskell has a special type called IO
– which you can think of as Recipe
type Recipe a = IO a
A value of type Recipe a
is
a description of a computation
that when executed (possibly) performs side effects and
produces a value of type
a
Recipes are Pure
Baking a cake can have side effects:
make your oven hot
make your floor dirty
set off your fire alarm
But: merely writing down a cake recipe does not cause any side effects
Executing Recipes
When executing a program, Haskell looks for a special value:
main :: Recipe ()
This is a recipe for everything a program should do
- that returns a unit
()
- i.e. does not return any useful value
The value of main
is handed to the runtime system and executed
The Haskell runtime is a master chef who is the only one allowed to produce effects!
Importantly:
A function of type
Int -> Int
still computes a single integer output from a single integer input and does nothing elseA function of type
Int -> Recipe Int
computes anInt
-recipe from a single integer input and does nothing elseOnly if I hand this recipe to
main
will any effects be produced
Writing Apps
To write an app in Haskell, you define your own recipe main
!
Hello World
main :: Recipe ()
main = putStrLn "Hello, world!"
putStrLn :: String -> Recipe ()
The function putStrLn
- takes as input a
String
- returns a
Recipe ()
for printing things to screen
… and we can compile and run it
$ ghc hello.hs
$ ./hello
Hello, world!
This was a one-step recipe
Most interesting recipes have multiple steps
- How do I write those?
QUIZ: Combining Recipes
Assume we had a function combine
that lets us combine recipes like so:
main :: Recipe ()
main = combine (putStrLn "Hello,") (putStrLn "World!")
-- putStrLn :: String -> Recipe ()
-- combine :: ???
What should the type of combine
be?
(A) () -> () -> ()
(B) Recipe () -> Recipe () -> Recipe ()
(C) Recipe a -> Recipe a -> Recipe a
(D) Recipe a -> Recipe b -> Recipe b
(E) Recipe a -> Recipe b -> Recipe a
Using Intermediate Results
Next, lets write a program that
- Asks for the user’s
name
using
getLine :: Recipe String
- Prints out a greeting with that
name
using
putStrLn :: String -> Recipe ()
Problem: How to pass the output of first recipe into the second recipe?
QUIZ: Using Yolks to Make Batter
Suppose you have two recipes
crack :: Recipe Yolk
eggBatter :: Yolk -> Recipe Batter
and we want to get
mkBatter :: Recipe Batter
mkBatter = crack `combineWithResult` eggBatter
What should the type of combineWithResult
be?
(A) Yolk -> Batter -> Batter
(B) Recipe Yolk -> (Yolk -> Recipe Batter) -> Recipe Batter
(C) Recipe a -> (a -> Recipe a ) -> Recipe a
(D) Recipe a -> (a -> Recipe b ) -> Recipe b
(E) Recipe Yolk -> (Yolk -> Recipe Batter) -> Recipe ()
Recipes are Monads
Wait a second, the signature:
combineWithResult :: Recipe a -> (a -> Recipe b) -> Recipe b
looks just like:
(>>=) :: m a -> (a -> m b) -> m b
In fact, in the standard library Recipe
is an instance of Monad
!
instance Monad Recipe where
(>>=) = {-... combineWithResult... -}
So we can put this together with putStrLn
to get:
main :: Recipe ()
main = getLine >>= \name -> putStrLn ("Hello, " ++ name ++ "!")
or, using do
notation the above becomes
main :: Recipe ()
main = do name <- getLine
putStrLn ("Hello, " ++ name ++ "!")
Exercise
Experiment with this code at home:
- Compile and run.
- Modify to repeatedly ask for names.
- Extend to print a “prompt” that tells you how many iterations have occurred.
Monads are Amazing
This code stays the same:
eval :: Expr -> Interpreter Int
eval (Num n) = return n
eval (Plus e1 e2) = do v1 <- eval e1
v2 <- eval e2
return (v1 + v2)
...
We can change the type Interpreter
to implement different effects:
type Interpreter a = Except String a
if we want to handle errorstype Interpreter a = State Int a
if we want to have a countertype Interpreter a = ExceptT String (State Int) a
if we want bothtype Interpreter a = [a]
if we want to return multiple results- …
Monads let us decouple two things:
- Application logic: the sequence of actions (implemented in
eval
) - Effects: how actions are sequenced (implemented in
>>=
)
Monads are Influential
Monads have had a revolutionary influence in PL, well beyond Haskell, some recent examples
Big data pipelines e.g. LinQ and TensorFlow
Thats all, folks!