Applicative Functors

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.

  1. pure id `apply` v = v
  2. pure (.) `apply` u `apply` v `apply` w = u `apply` (v `apply` w)
  3. pure f `apply` pure x = pure (f x)
  4. u `apply` pure y = pure ($ y) `apply` u

Maybe

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

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

Functions

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

From 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").

Source Files