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 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
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, since
f <$> x = pure f <*> x`.