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 Applicatives 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, sincef <$> 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