Lecture 20 — 2015-11-16
Monads
This lecture is written in literate Haskell; you can download the raw source.
module Lec20 where
import Control.Applicative
import Control.Monad
import Data.Char
import Data.List hiding (group)
import qualified Data.Map as Map
import Data.Map (Map)
import Data.Maybe
import Numeric
newtype Parser a = Parser { parse :: String -> Maybe (a,String) }
instance Functor Parser where
fmap f p = Parser $ \s ->
case parse p s of
Nothing -> Nothing
Just (v,s') -> Just (f v,s')
instance Applicative Parser where
pure a = Parser $ \s -> Just (a,s)
f <*> a = Parser $ \s ->
case parse f s of
Nothing -> Nothing
Just (g,s') -> parse (fmap g a) s'
instance Alternative Maybe where
-- empty :: f a
empty = Nothing
-- (<|>) :: f a -> f a -> f a
Just x <|> _ = Just x
Nothing <|> r = r
-- empty <|> f == f
-- f <|> empty == f
instance Alternative Parser where
empty = Parser $ \s -> empty
l <|> r = Parser $ \s -> parse l s <|> parse r s
some p = (:) <$> p <*> many p
many p = some p <|> pure []
satisfy :: (Char -> Bool) -> Parser Char
satisfy p = Parser f
where f [] = Nothing
f (c:s) = if p c then Just (c,s) else Nothing
ensure :: (a -> Bool) -> Parser a -> Parser a
ensure pred p = Parser f
where f s = case parse p s of
Just (a,s') | pred a -> Just (a,s')
_ -> Nothing
lookahead :: Parser (Maybe Char)
lookahead = Parser f
where f [] = Just (Nothing,[])
f (c:s) = Just (Just c,c:s)
-- those three are the basis
spaces :: Parser ()
spaces = pure () <* many (satisfy isSpace)
char :: Char -> Parser Char
char c = spaces *> satisfy (==c)
str :: String -> Parser String
str s = spaces *> (sequenceA $ map (satisfy . (==)) s)
int :: Parser Int
int = read <$> some (satisfy isDigit)
opt :: Alternative f => f a -> f (Maybe a)
opt f = (Just <$> f) <|> (pure Nothing)
eof :: Parser ()
eof = pure () <* ensure isNothing lookahead
We were able to quickly and easily add negation/minus to our language…
data Arith =
Num Int |
Plus Arith Arith |
Times Arith Arith |
Neg Arith deriving Show
term :: Parser Arith
term = liftA2 Plus (factor <* char '+') term
<|> liftA2 (\a b -> Plus a (Neg b)) (factor <* char '-') term
<|> factor
factor :: Parser Arith
factor = liftA2 Times (neg <* char '*') factor <|> neg
neg :: Parser Arith
neg = Neg <$> (char '-' *> atom) <|> atom
atom :: Parser Arith
atom = num <|> char '(' *> term <* char ')'
num :: Parser Arith
num = spaces *> (Num <$> int)
We can come up with a Read
instance using our parsers.
readS :: Parser a -> ReadS a
readS (Parser p) s = maybeToList $ p s
instance Read Arith where
readsPrec _ = readS term
We didn’t see it in class, but here’s an equivalent definition for Applicative
.
class Functor f => Monoidal f where
unit :: f ()
(***) :: f a -> f b -> f (a,b)
u :: Applicative f => f ()
u = pure ()
prod :: Applicative f => f a -> f b -> f (a,b)
prod a b = (,) <$> a <*> b
p :: Monoidal f => a -> f a
p x = const x <$> unit
appl :: Monoidal f => f (a -> b) -> f a -> f b
appl f a = uncurry ($) <$> f *** a
Why is it called Monoidal
? It’s because it behaves like a monoid:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
Finally, note that we can define fmap
from (<*>)
.
fmapFromAp :: Applicative f => (a -> b) -> f a -> f b
fmapFromAp f fa = pure f <*> fa
-- try to write a parser for the language
--
-- (int |list of ints of length of first int|)+
groupEg = "4 15 16 17 18 1 19 0 0 5 20 21 22 23 24"
group' :: Parser (Parser [Int])
group' = (\n -> sequenceA $ replicate n int) <$> int
Drat, it has the wrong type! We need something like:
joinUp :: Monad f => f (f a) -> f a
We looked at a related type: (>>=)
, pronounced “bind”, has type f a -> (a -> f b) -> f b
.
joinUp = (>>= id)
Monads
class Applicative m => Monad m where
return :: a -> m a -- same as pure!
(>>=) :: m a -> (a -> m b) -> m b
Note that we can define ap
a/k/a (<*>)
in terms of >>=
:
fakeAp :: Monad f => f (a -> b) -> f a -> f b
fakeAp fab fa =
fab >>= \f ->
fa >>= \a ->
return $ f a
Ditto fmap
a/k/a (<$>)
:
fakeFmap :: Monad f => (a -> b) -> f a -> f b
fakeFmap f fa =
fa >>= \a ->
return $ f a
What’s it look like for Maybe
? Here’s its cousin, CouldBe
:
data CouldBe a = Nope | Yep a deriving Show
instance Functor CouldBe where
fmap f Nope = Nope
fmap f (Yep a) = Yep $ f a
instance Applicative CouldBe where
pure = Yep
Nope <*> _ = Nope
_ <*> Nope = Nope
(Yep f) <*> (Yep x) = Yep $ f x
instance Monad CouldBe where
return = pure
Nope >>= f = Nope
(Yep x) >>= f = f x
name :: Maybe String -> Maybe String -> Maybe String
name given family =
given >>= \first ->
if first `elem` ["Prince","Madonna"]
then return $ first
else
family >>= \last ->
return $ first ++ " " ++ last
drdave = name (Just "Dave") (Just "Kauchak")
prince = name (Just "Prince") Nothing
We used the Maybe
monad to write a simple typechecker. It saved us tons of boilerplat.
type Id = String
data Expr =
EVar Id
| ETrue
| EFalse
| EIf Expr Expr Expr
| ENum Int
| EIncr Expr
| EDecr Expr
| EIsZero Expr
| EApp Expr Expr
| ELam Id Type Expr
deriving (Show, Eq)
data Type =
TyBool
| TyNum
| TyArrow Type Type
deriving (Show, Eq)
type Context = Map Id Type
sameType :: Type -> Type -> Maybe a -> Maybe a
sameType t1 t2 a | t1 == t2 = a
sameType _ _ _ = Nothing
typeCheck :: Context -> Expr -> Maybe Type
typeCheck ctx (EVar id) = Map.lookup id ctx
typeCheck ctx ETrue = return TyBool
typeCheck ctx EFalse = return TyBool
typeCheck ctx (EIf e1 e2 e3) =
typeCheck ctx e1 >>= \t1 ->
sameType t1 TyBool $
typeCheck ctx e2 >>= \t2 ->
typeCheck ctx e3 >>= \t3 ->
sameType t2 t3 $ return t2
typeCheck ctx (ENum n) = return TyNum
typeCheck ctx (EIncr e) =
typeCheck ctx e >>= \t ->
sameType t TyNum $ return TyNum
typeCheck ctx (EDecr e) =
typeCheck ctx e >>= \t ->
sameType t TyNum $ return TyNum
typeCheck ctx (EIsZero e) =
typeCheck ctx e >>= \t ->
sameType t TyNum $ return TyBool
typeCheck ctx (EApp e1 e2) =
typeCheck ctx e1 >>= \t ->
typeCheck ctx e2 >>= \t1' ->
case t of
TyArrow t1 t2 -> sameType t1 t1' $ return t2
_ -> Nothing
typeCheck ctx (ELam x t1 e) =
typeCheck (Map.insert x t1 ctx) e >>= \t2 ->
return $ TyArrow t1 t2
I also pointed out that we could have written:
typeCheck ... >>= return . TyArrow t1
To be sure, we can now write the grouped language example from above.
instance Monad Parser where
return = pure
p >>= f = Parser $ \s ->
case parse p s of
Nothing -> Nothing
Just (v,s') -> parse (f v) s'
group :: Parser [Int]
group =
(spaces *> int) >>= \n ->
sequenceA $ replicate n (spaces *> int)
groups = some group