Applicatives in Pictures
From fmap to <*>: combining independent values that live inside a shared computational context.
Functors gave us a wonderfully useful operation:
fmap :: (a -> b) -> f a -> f b
Give fmap an ordinary function and a value inside some context, and the function travels into that context.
But now consider a slightly stranger situation. What if the function itself is already inside the context?
Just (+1) -- a function in Maybe
Just 5 -- a value in Maybe
How do we combine them without opening the boxes by hand?
That is the doorway to Applicative.
The two operations to remember
An Applicative gives us two central operations:
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
pure places an ordinary value into the context. <*>, pronounced apply, applies a contextual function to a contextual value.
For Maybe:
pure 5 :: Maybe Int
-- Just 5
Just (+1) <*> Just 5
-- Just 6
Why currying suddenly becomes beautiful
Haskell functions are curried. The type
(+) :: Num a => a -> a -> a
can be read as:
(+) :: a -> (a -> a)
So after supplying one argument, we still have a function waiting for the second argument.
That means we can do this:
pure (+) <*> Just 10 <*> Just 32
-- Just 42
or, more idiomatically:
(+) <$> Just 10 <*> Just 32
-- Just 42
The first <$> is simply fmap written infix.
sum3 :: Num a => Maybe a -> Maybe a -> Maybe a -> Maybe a
sum3 x y z = (\a b c -> a + b + c) <$> x <*> y <*> z
example1 = sum3 (Just 10) (Just 20) (Just 12)
-- Just 42
example2 = sum3 (Just 10) Nothing (Just 12)
-- NothingOne missing input makes the whole Maybe computation unavailable.Building records without case-analysis soup
Suppose we want to construct a person only when all three fields are present.
data Person = Person
{ name :: String
, age :: Int
, city :: String
}
deriving Show
A manual version quickly becomes noisy:
mkPerson :: Maybe String -> Maybe Int -> Maybe String -> Maybe Person
mkPerson mName mAge mCity =
case mName of
Nothing -> Nothing
Just name ->
case mAge of
Nothing -> Nothing
Just age ->
case mCity of
Nothing -> Nothing
Just city -> Just (Person name age city)
Applicative expresses the same structure almost exactly as we would say it:
mkPerson :: Maybe String -> Maybe Int -> Maybe String -> Maybe Person
mkPerson mName mAge mCity =
Person <$> mName <*> mAge <*> mCity
The list Applicative is delightfully different
With lists, <*> means: apply every function to every value.
[(+1), (*10)] <*> [1, 2, 3]
produces:
[2, 3, 4, 10, 20, 30]
The same interface, <*>, captures a completely different notion of context.
(,) <$> ["tea", "coffee"] <*> ["Marie", "Parle-G"]
becomes:
[ ("tea", "Marie")
, ("tea", "Parle-G")
, ("coffee", "Marie")
, ("coffee", "Parle-G")
]
pure is not really about purity
The name can initially mislead. pure does not mean “make this function pure.” It means “take this ordinary value and place it into the minimal context.”
pure 7 :: Maybe Int
-- Just 7
pure 7 :: [Int]
-- [7]
This lets ordinary values participate in Applicative expressions without special machinery.
A useful mental ladder
There is a lovely progression here:
ordinary application
f x
Functor
f <$> fx
Applicative
ff <*> fx
Or, in types:
(a -> b) -> a -> b
(a -> b) -> f a -> f b
f (a -> b) -> f a -> f b
Each step moves another piece of the computation into the context.
When Applicative is exactly enough
Applicative is excellent when the computations are independent.
For example, parsing three form fields:
User <$> parseName rawName
<*> parseAge rawAge
<*> parseCity rawCity
We already know all three computations before any one of them runs.
But what if the next computation depends on the actual value produced by the previous one?
For instance:
read a user id
↓
use that id to decide which account to fetch
↓
use that account to decide which permissions to load
Now the structure itself is value-dependent.
Applicative cannot express that dependency by itself.
That is precisely where Monad enters.
Predict the result without running GHCi:
(*) <$> Just 6 <*> Just 7What happens if the second Just 7 is replaced by Nothing?
Reveal solution
The first expression is Just 42. Replacing either required argument with Nothing gives Nothing.
Write an Applicative expression that creates a triple from three Maybe values.
The target type is:
triple :: Maybe a -> Maybe b -> Maybe c -> Maybe (a, b, c)Reveal solution
triple :: Maybe a -> Maybe b -> Maybe c -> Maybe (a, b, c)
triple ma mb mc = (,,) <$> ma <*> mb <*> mcThe tuple constructor (,,) is simply a curried three-argument function.