Lecture 9.0 — 2017-02-15
Applicative
This lecture is written in literate Haskell; you can download the raw source.
A lot of this material is borrowed/based on two resources written by Brent Yorgey: his Haskell course from Spring 2013 and his Typeclassopedia. I encourage you to go read these excellent resources!
A problem in Functortown
We looked at how we might define arithmetic evaluation with the possibility of errors:
evalA' :: Store -> AExp -> Either Error Int`
evalA' st (Neg e) = myNegate $ evalA' st e
where myNegate (Left err) = Left err
myNegate (Right n) = Right $ negate n
We observed that myNegate = fmap negate
, so we can instead write:
evalA' :: Store -> AExp -> Either Error Int`
evalA' st (Neg e) = negate <$> evalA' st e
(Recall that (<$>)
is the same thing as fmap
, but infix—it’s deliberately designed to look like application, ($)
.)
We then tried to work out the case for Plus
:
evalA' :: Store -> AExp -> Either Error Int`
evalA' st (Neg e) = negate <$> evalA' st e
evalA' st (Plus e1 e2) = fmap2 (+) (evalA' st e1) (evalA' st e2)
What would this function fmap2
be? For our concrete example, we succeeded in defining a function:
fmap2 :: (a -> b -> c) -> Either e a -> Either e b -> Either e c
fmap2 _ (Left err1) _ = Left err1
fmap2 _ _ (Left err2) = Left err2
fmap2 f (Right v1) (Right v2) = Right $ f v1 v2
But can we go more general? We tried writing a different function fmap2
, but that didn’t go well either.
fmap2 :: Functor f => (a -> b -> c) -> f a -> f b -> f c
fmap2 f a b = undefined
where fa = fmap f a -- where do we go from here?
Functor
is a fine type class, but it only lets us operate opaquely. Once a function is trapped in Functor
, we can’t get it out!
A different type class, Applicative
, solves this problem.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b -- pronounced 'ap'
NB that Applicative
is a subclass of Functor
.
fmap2' :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
fmap2' f a b = (fmap f a) <*> b
pure :: a -> f a
fmap :: (a -> b) -> f a -> f b
fmap2 :: (a -> b -> c) -> f a -> f b -> f c
Note that to be an Applicative
, you must be a Functor
. Can we write fmap
using the <*>
?
fmap' :: Applicative f => (a -> b) -> f a -> f b
fmap' f a = pure f <*> a
In fact, using fmap
with Applicative
s is so common that we have the definition <$> = fmap
.
fmap2Best :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
fmap2Best f a b = f <$> a <*> b -- note left associativity and tight binding of <$>
Finally, note that fmap2
is called liftA2
in Haskell.
Obey the laws
Like Functor
, the Applicative
type class is governed by laws.
Identity: pure id <*> v = v
Composition: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
Homomorphism: pure f <*> pure x = pure (f x)
Interchange: u <*> pure y = pure ($ y) <*> u
Note that identity
is a generalization of id <$> v = v
from Functor, since
f <$> x = pure f <*> x`.
Another way in
type Name = String
data Employee = Employee { name :: Name
, phone :: String }
deriving Show
maybeEmployee :: (Name -> String -> Employee) ->
(Maybe Name -> Maybe String -> Maybe Employee)
maybeEmployee f (Just n) (Just p) = Just $ f n p
maybeEmployee _ _ _ = Nothing
listEmployee :: (Name -> String -> Employee) ->
([Name] -> [String] -> [Employee])
listEmployee f n p = zipWith f n p
listEmployee' :: (Name -> String -> Employee) ->
([Name] -> [String] -> [Employee])
listEmployee' f n p = map (\(name,phone) -> f name phone) allPairs
where allPairs = Prelude.concat (map (\name -> map (\phone -> (name,phone)) p) n)
-- map (uncurry f) $ concatMap (\name -> map (\phone -> (name,phone)) p) n
funEmployee :: (Name -> String -> Employee) ->
(e -> Name) -> (e -> String) -> (e -> Employee)
funEmployee f mkName mkPhone = \e -> f (mkName e) (mkPhone e)
Another example of Applicative
, highlighting how it bears some similarity to normal “application”:
name :: Maybe String -> Maybe String -> Maybe String
name given family = (++) <$> given <*> family
drdave = name (Just "Dave") (Just "Kauchak")
prince = name (Just "Prince") Nothing