Monads

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

  1. Functors
  2. Monads
  3. 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

  1. Functors [done]
  2. Monads for

    2.1. Error Handling

    2.2. Mutable State

  3. 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 result res
  • If res is an Error, just return that error
  • If res is a Value v then do further processing on v



case res of
  Error err -> Error err
  Value v   -> process v -- do more stuff with v



Bottling a Magic Pattern

Bottling a Magic Pattern

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 evaluation
  • a -> 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

  1. Functors [done]
  2. Monads for

    2.1. Error Handling [done]

    2.2. Mutable State

  3. 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 to eval), 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)
  1. Easy to make a mistake, e.g. pass cnt instead of cnt1 into the second eval!
  2. 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 counter cnt
  • Get a result (cnt', v)
  • Then do further processing on v using the new counter cnt'
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 and put?
  • 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

  1. Functors [done]
  2. Monads for

    2.1. Error Handling [done]

    2.2. Mutable State [done]

  3. 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 else

  • Moreover, 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

Cake vs. Recipe

Cake vs. Recipe

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

Baker Aker

Baker Aker

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 else

  • A function of type Int -> Recipe Int computes an Int-recipe from a single integer input and does nothing else

  • Only 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

  1. Asks for the user’s name using
    getLine :: Recipe String
  1. 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:

  1. Compile and run.
  2. Modify to repeatedly ask for names.
  3. 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 errors
  • type Interpreter a = State Int a if we want to have a counter
  • type Interpreter a = ExceptT String (State Int) a if we want both
  • type Interpreter a = [a] if we want to return multiple results



Monads let us decouple two things:

  1. Application logic: the sequence of actions (implemented in eval)
  2. Effects: how actions are sequenced (implemented in >>=)








Monads are Influential

Monads have had a revolutionary influence in PL, well beyond Haskell, some recent examples

  • Error handling in go e.g. 1
    and 2

  • Asynchrony in JavaScript e.g. 1 and 2

  • Big data pipelines e.g. LinQ and TensorFlow








Thats all, folks!