Lecture 11 — 2017-10-03
Monads
This lecture is written in literate Haskell; you can download the raw source.
import Control.Applicative
import Data.IORef
import System.IO
After some logistics, we tried to write a parser for a language like the following:
n, m are natural numbers
n : m_1 m_2 ... m_n
GOOD EXAMPLES:
0 :
1 : 2
2 : 20 27
BAD EXAMPLES:
0 : 73 5 3
2 : 1
hello
We want our parser to return the list of ints that comes after the count. We can write the first part pretty easily:
count :: Parser Int
count = num <* char ':'
And, given the count, we can write the rest with some helper functions:
list :: Int -> Parser [Int]
list n = sequenceA (replicate n num) <* eof
But how do we bind them together? We need a new operation that let’s us combine things:
class Applicative f => Monad f where
(>>=) :: f a -> (a -> f b) -> f b
Using this operation, we can easily define what we’re looking for:
instance Monad Parser where
-- (>>=) :: Parser a -> (a -> Parser b) -> Parser b
p1 >>= k = Parser $ \s ->
case parse p1 s of
Just (v,s') -> parse (k v) s'
Nothing -> Nothing
lang :: Parser [Int]
lang = count >>= list
Other Monad
instances
instance Monad Maybe where
(Just v) >>= k = k v
Nothing >>= _ = Nothing
instance Monad (Either e) where
(Right v) >>= k = k v
(Left e) >>= _ = Left e
We compared our various functions, using (=<<) :: Monad f => (a -> f b) -> f a -> f b
a/k/a “reverse bind” for better symmetry:
($) :: (a -> b) -> a -> b -- a/k/a apply
(<$>) :: Functor f => (a -> b) -> f a -> f b -- a/k/a fmap
(<*>) :: Applicative f => f (a -> b) -> f a -> f b -- a/k/a ap
(=<<) :: Monad f => (a -> f b) -> f a -> f b -- a/k/a reverse bind
Recall that Monad
is a sub-class of Applicative
, and Applicative
is a sub-class of Functor
. Each adds expressivity: it’s possible to define (<*>)
using (>>=)
or (=<<)
and pure
; it’s possible to define (<$>)
using (<*>)
and pure
.
We briefly mentioned an alternative operation, join :: Monad f => f (f a) -> f a
. We’ll see that join
and (>>=)
are equivalent—each can encode the other.
The IO
monad
We learned that main :: IO ()
is the entry point for Haskell programs, playing around with some basic IO actions:
main = putStr "What's your name? " >> hFlush stdout >> getLine >>= \s -> putStrLn ("Hello, " ++ s ++ "!")
We also played with IORef a
, the type of references, i.e., mutable variables.
refs = newIORef True >>= \ref ->
writeIORef ref False >>
readIORef ref
But, dangerously, you’ll get the following in GHCi:
let a = newIORef True
a >>= \ref -> writeIORef ref False -- set the ref?
a >>= readIORef -- yields True! what?!
a >>= \ref -> writeIORef ref False >> readIORef ref -- yields False
The issue is one of staging: a value of type IO a
hasn’t actually performed the action—it’s a script or recipe for the actions to be performed. The recipe for a
above can be used more than once, but each time we’re composing recipes, not computations. That is, a
is a recipe that allocates a new reference cell that holds booleans, with True
as its initial value.
The first command we actually run, a >>= \ref -> writeIORef ref False
yields a new recipe, where we allocate the cell and then write False
to it. Haskell runs this recipe, allocating a new cell and writing to it. So far so good. Our second recipe allocates a new cell (with True
in it) and reads from it. The first recipe has no bearing whatsoever on this second recipe. If we wanted the written and read reference cells to be related, we have to carefully do so, as in the third recipe.