class Functor_ t => Applicative_ (t :: * -> *) where
pure :: a -> t a
apply :: t (a -> b) -> t a -> t b
Several laws (in addition to those for Functor
) which we
won’t stare at too closely for now.
pure id `apply` v
≡ v
pure (.) `apply` u `apply` v `apply` w
≡ u `apply` (v `apply` w)
pure f `apply` pure x
≡ pure (f x)
u `apply` pure y
≡ pure ($ y) `apply` u
Recall pureMaybe
and applyMaybe
:
instance Applicative_ Maybe where
pure :: a -> Maybe a
pure = Just
apply :: Maybe (a -> b) -> Maybe a -> Maybe b
apply (Just f) ma = fmap f ma
apply _ _ = Nothing
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
pure :: a -> [a]
pure a = [a]
apply :: [(a -> b)] -> [a] -> [b]
-- apply fs xs = concatMap (\f -> map (\x -> f x) xs) fs
-- apply fs xs = concatMap (\f -> concatMap (\x -> [f x]) xs) fs
apply fs xs = [ f x | f <- fs, x <- xs ]
A second way is 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
fmap :: (a -> b) -> ZipList a -> ZipList b
fmap f (ZipList xs) = ZipList (fmap f xs)
instance Applicative_ ZipList where
apply :: ZipList (a -> b) -> ZipList a -> ZipList b
apply (ZipList fs) (ZipList xs) = ZipList $ zipWith ($) fs xs
pure :: a -> ZipList a
pure f = ZipList $ repeat f
Note: Okay, if you still haven’t seen newtype
and newtype
(Redux), now really is the time to read it. The library is full of
wrapper types to facilitate plugging definitions into various type
classes.
instance Applicative_ ((->) t) where
-- pure :: a -> ((->) t) a
pure :: a -> (t -> a)
-- pure a = \t -> a
pure = 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)
An fmap
example with functions:
> pure (^2) `apply` (3*) $ 3 -- same as: (^2) `fmap` (3*) $ 3
-- apply (const (^2)) (3*) $ 3
-- (\t1 -> const (^2) t1 ((3*) t1)) $ 3
-- const (^2) 3 ((3*) 3)
-- (\_ -> (^2)) 3 ((3*) 3)
-- (^2) ((3*) 3)
-- (^2) 9
81
An apply
example with functions:
> liftA2 (+) (^2) (3*) 3
-- pure (+) `apply` (^2) `apply` (3*) $ 3
-- const (+) `apply` (^2) `apply` (3*) $ 3
-- apply (apply (const (+)) (^2)) (3*) $ 3
-- (\t1 -> (apply (const (+)) (^2)) t1 ((3*) t1)) $ 3
-- (\t1 -> (\t2 -> (const (+)) t2 ((^2) t2)) t1 ((3*) t1)) $ 3
-- (\t2 -> (const (+)) t2 ((^2) t2)) 3 ((3*) 3)
-- (const (+)) 3 ((^2) 3) ((3*) 3)
-- (\_ -> (+)) 3 ((^2) 3) ((3*) 3)
-- (+) ((^2) 3) ((3*) 3)
-- (+) 9 ((3*) 3)
-- (+) 9 9
18
Another example:
> liftA3 (,,) show (^2) (3*) 3
("3",9,9)
Each of the functions being composed (here, show
and
(^2)
and (3*)
) gets to “read” the
“environment” value (here 3
). We’ll return to this
terminology later. For now, let’s rewrite the instance with a different
type variable, to suggest an env
ironment:
instance Applicative_ ((->) env) where
pure :: a -> (env -> a)
pure a = \env -> a
apply :: (env -> a -> b) -> (env -> a) -> (env -> b)
apply f g = \env -> f env (g env)
Applicative
The Applicative
class defined in Control.Applicative
— and exposed by Prelude
— uses the name
(<*>)
rather than apply
. (We alluded to
this choice in the “Monad
Roadmap”).
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_.pure
The Control.Applicative
library defines
liftA2
, liftA3
, etc. functions analogous to
the lift2Maybe
, lift3Maybe
, etc. functions we
defined before.
Actually, liftA2
is a method inside Applicative
(rather than a helper
functiond defined outside) and is an alternative to
(<*>)
for a minimal complete definition.
There are also versions of (<*>)
that “keep” the
left or right arguments and “ignore” the other:
(<*) :: Applicative f => f a -> f b -> f a
(*>) :: Applicative f => f a -> f b -> f b
Think about how to define these “for free” in terms of
(<*>)
. These may look bizarre, but we’ll see cases in
which these operators are quite handy (stay tuned for “parser
pipelines”).