Applicatives: Functions in Context
Functor lets you map a plain function over a context. Applicative lets the function itself be wrapped in that same context — which is exactly what you need to combine several independent effectful values.
Picking up where Functor left off
Chapter 9 ended on a gap: fmap needs a plain function, a -> b. What if the function is itself wrapped — Just (+3) instead of bare (+3)? ((+3) is a section — see Chapter 1 if that’s unfamiliar; it’s just an ordinary one-argument function.) Applicative extends Functor with exactly the operation this needs:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
pure lifts an ordinary value into the context with no extra effect attached. <*> (pronounced “apply,” or informally “ap”) takes a wrapped function and a wrapped argument, and produces a wrapped result:
instance Applicative Maybe where
pure x = Just x
Nothing <*> _ = Nothing
Just f <*> x = fmap f x
Just (+3) <*> Just 5 -- Just 8
Nothing <*> Just 5 -- Nothing
Just (+3) <*> Nothing -- Nothing
Why this matters: combining independent values
The single most common use of Applicative is combining several wrapped values with an ordinary, multi-argument function — something Functor alone genuinely cannot do:
validateAge :: Int -> Maybe Int
validateAge n
| n >= 0 && n < 150 = Just n
| otherwise = Nothing
validateName :: String -> Maybe String
validateName s
| not (null s) = Just s
| otherwise = Nothing
data Person = Person String Int deriving Show
mkPerson :: String -> Int -> Maybe Person
mkPerson name age = Person <$> validateName name <*> validateAge age
mkPerson "Ada" 36 -- Just (Person "Ada" 36)
mkPerson "" 36 -- Nothing
mkPerson "Ada" (-1) -- Nothing
Read Person <$> validateName name <*> validateAge age left to right: Person <$> validateName name maps the two-argument constructor Person over a Maybe String, producing a Maybe (Int -> Person) — a function still waiting for its second argument, wrapped in Maybe. <*> validateAge age then supplies that argument, also wrapped. If either validation fails, the whole chain short-circuits to Nothing, with no explicit case or nested pattern matching required.
This chaining pattern — f <$> a <*> b <*> c <*> ... — is common enough in real Haskell to have its own name, the “applicative style,” and it scales cleanly to any number of arguments. It’s a direct generalization of fmap, which only ever handles one.
Lists as Applicative: every combination
Maybe’s Applicative instance models “fail fast.” Lists’ instance models something entirely different: every possible combination.
instance Applicative [] where
pure x = [x]
fs <*> xs = [f x | f <- fs, x <- xs]
[(+1), (*2)] <*> [10, 20]
-- [11,21,20,40] -- (+1) applied to both, THEN (*2) applied to both
The same <*> operator, the same Applicative typeclass — but because the “context” being combined is non-determinism (a list of possibilities) rather than possible failure (Maybe), the resulting behaviour is completely different: instead of short-circuiting, it explores every pairing. This is the payoff of building on top of a shared abstraction: you learn <*> once, and its meaning specializes sensibly to whatever context you’re working in.
It’s tempting to assume <*> always means “apply the function to the argument, and combine any failures” the way Maybe’s instance suggests. Watch out for that assumption leaking into how you read []’s instance (all combinations, no failure concept at all) or IO’s instance (run one effectful action, then the other, in order) — the shape of <*> is fixed by the typeclass, but its meaning is entirely up to each instance, as long as the laws below hold.
The laws
Applicative instances must satisfy identity, composition, homomorphism, and interchange laws — the two most load-bearing in practice:
The first says wrapping the identity function and applying it changes nothing (consistent with Functor’s identity law). The second — the homomorphism law — says that applying a plain function to a plain value, then wrapping the result, is the same as wrapping both first and then applying: pure genuinely adds no extra effect of its own, it’s a pure “lift,” exactly as its name promises.
Every Applicative gives rise to a Functor (via fmap f x = pure f <*> x), which is why the typeclass hierarchy requires Functor f => Applicative f. Category theorists describe Applicative as a lax monoidal functor — a functor that also knows how to combine two separate wrapped values into one wrapped pair, coherently. That’s a denser way of saying exactly what mkPerson did above: combine several independent context-carrying values into one.
Applicative can combine independent effects, but it can’t let a later effect depend on the result of an earlier one — validateAge above doesn’t get to look at the validated name. That extra power — sequencing, where each step can inspect what came before — is exactly what Chapter 11’s Monad adds.