Lecture 9.0 — 2016-09-27

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!

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)

Can we use Functor to do this? The type above looks like fmap, but with two arguments instead of one.

First we tried writing:

fEmployee :: Functor f => f Name -> f String -> f Employee
fEmployee = undefined

But we got stuck. Then 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

Another example of Applicative, highlighting how it bears some s:

name :: Maybe String -> Maybe String -> Maybe String
name given family = (++) <$> given <*> family

drdave = name (Just "Dave") (Just "Kauchak")

prince = name (Just "Prince") Nothing

We wrote up a straightforward instance for Maybe and a more interesting instance for Either e:

instance Applicative (Either e) where
  pure x = Right x -- because Left x would be ill typed!
  (Right f) <*> (Right v) = Right $ f v
  err@(Left e) <*> _ = err
  _ <*> err@(Left e) = err

Then we went over the Applicative definitions for lists.

instance Applicative [] where
  pure x = [x]

  []     <*>  _ = []
  _      <*> [] = []
  (f:fs) <*> xs = map f xs ++ fs <*> xs
newtype ZipList a = ZipList { getZipList :: [a] }
  deriving (Eq, Show, Functor)

instance Applicative ZipList where
  pure = ZipList . repeat
  ZipList fs <*> ZipList xs = ZipList (zipWith ($) fs xs)
sequenceA :: (Applicative f) => [f a] -> f [a]
sequenceA [] = pure []
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs

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`.