Applicative Functors
class Functor_ t => Applicative_ (t :: * -> *) where
lift :: a -> t a
apply :: t (a -> b) -> t a -> t b
Several laws (in addition to Functor
)
which we won't stare at too closely for now.
lift id `apply` v = v
lift (.) `apply` u `apply` v `apply` w = u `apply` (v `apply` w)
lift f `apply` lift x = lift (f x)
u `apply` lift y = lift ($ y) `apply` u
Maybe
Recall liftMaybe
and applyMaybe
:
instance Applicative_ Maybe where
-- lift :: a -> Maybe a
lift = Just
-- apply :: Maybe (a -> b) -> Maybe a -> Maybe b
apply (Just f) (Just a) = Just $ f a
apply _ _ = Nothing
Two Interpretations for Lists
Can think of lists as two different "computational contexts".
One way is to consider a list as a set values denoting a non-determinstic choice. Under this interpretation, want to apply all functions to all arguments.
instance Applicative_ [] where
-- lift :: a -> [a]
lift a = [a]
-- apply :: [(a -> b)] -> [a] -> [b]
-- apply fs xs = concatMap (\f -> Prelude.map (\x -> f x) xs) fs
apply fs xs = [ f x | f <- fs, x <- xs ]
A second way to consider a list as an ordered sequence of values. Under this interpretation, want to pairwise apply functions to arguments. Because we can only define one instance per type, we need to define a wrapper for (at least) one or the other; the Haskell libraries choose the instance above for "bare" lists and the following wrapper types for the second interpretation, called zip lists.
newtype ZipList a = ZipList { getZipList :: [a] }
instance Functor_ ZipList where
map f (ZipList xs) = ZipList (Prelude.map f xs)
instance Applicative_ ZipList where
-- apply :: ZipList (a -> b) -> ZipList a -> ZipList b
ZipList fs `apply` ZipList xs = ZipList $ zipWith ($) fs xs
-- lift :: a -> ZipList a
lift f = ZipList $ repeat f
Functions
instance Applicative_ ((->) t) where
-- lift :: a -> ((->) t) a
-- lift :: a -> (t -> a)
-- lift a = \t -> a
lift = const
-- apply :: ((->) t) (a -> b) -> ((->) t) a -> ((->) t) b
-- apply :: (t -> a -> b) -> (t -> a) -> (t -> b)
apply f g = \t -> f t (g t)
From Applicative_
to Applicative
The Applicative
class defined in Control.Applicative
(and exposed
by Prelude
) uses the name pure
rather than lift
and (<*>)
rather than apply
.
class Functor f => Applicative f where
-- fmap :: (a -> b) -> f a -> f b -- from Functor
(<*>) :: f (a -> b) -> f a -> f b -- Applicative_.apply
pure :: a -> f a -- Applicative_.lift
The Control.Applicative
library defines liftA2
, liftA3
,
etc. functions analogous to the lift2Maybe
, lift3Maybe
, etc.
functions we defined before.