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 = Just $ 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 f (ZipList xs) = ZipList (fmap f xs)
instance Applicative_ ZipList where
-- apply :: ZipList (a -> b) -> ZipList a -> ZipList b
ZipList fs `apply` ZipList xs = ZipList $ zipWith ($) fs xs
-- pure :: a -> ZipList a
pure f = ZipList $ repeat f
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
Applicative_
to 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.
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 (<*>)
. We'll see cases in which these operators are quite handy (stay tuned for "parser pipelines").