Category Theory Key idea: <*> applies a wrapped function to a wrapped argument

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.

Cool Fact

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.

Common Pitfall

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:

pure id <*> v=vpure f <*> pure x=pure (f x)\texttt{pure id <*> v} = v \qquad\qquad \texttt{pure f <*> pure x} = \texttt{pure (f x)}

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.

λA Category-Theoretic View

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.