Applicatives

Course 2 · Ch 2
Applicatives
Combining independent computations — the deliberate bridge between Functor and Chapter 3's own monad reveal

haskell2-1's fmap handles a plain function applied to one wrapped value. This chapter handles the next real gap: what happens when the function itself is wrapped too.

The Problem Functor Can't Solve

fmap :: (a -> b) -> f a -> f b -- but what about: Just (+3) applied to Just 5 ? -- the function ITSELF is wrapped — fmap's signature has no place for that

fmap requires a plain, unwrapped a -> b. It genuinely cannot express applying Just (+3) to Just 5 — the function is wrapped too, and nothing in fmap's own type signature has anywhere to put a wrapped function.

The Applicative Typeclass & <*>

class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b Just (+3) <*> Just 5 -- Just 8

Applicative requires Functor as a superclass — a real hierarchy: every Applicative is automatically a Functor too. <*> applies a wrapped function to a wrapped value, exactly the case fmap couldn't handle.

pure — Wrapping a Plain Value

pure 5 :: Maybe Int -- Just 5 pure 5 :: [Int] -- [5]

pure lifts an ordinary value into the applicative context using the minimal, default wrapping — genuinely useful for starting a chain of applicative operations from a plain, unwrapped value.

Combining Independent Computations

(+) <$> Just 3 <*> Just 4 -- Just 7

Here's the real, practical use case: combining two separately-wrapped values with an ordinary multi-argument function. java2-1's/csharp2-1's own generics have no equivalent mechanism for this at all — combining two independently-wrapped values with a plain function requires manual unwrapping in both, since neither language's generics carry this kind of combining operation as part of the type itself.

A Real Practical Example — Validating Multiple Fields

data Person = Person { name :: String, age :: Int } validateName :: String -> Maybe String validateAge :: Int -> Maybe Int mkPerson :: String -> Int -> Maybe Person mkPerson n a = Person <$> validateName n <*> validateAge a -- Nothing if EITHER field fails validation — Just a Person only if BOTH succeed

A genuinely realistic, common Haskell idiom — building a record from several independently-validated fields, where the whole construction fails if any single field does.

The Bridge to Monad

Stated explicitly, not just implied: Applicative combines independent computations — neither can depend on the other's actual result, only on whether each independently succeeded or failed. Chapter 3's own Monad is exactly what's needed once a later computation needs to depend on an earlier one's real, concrete value. Applicative isn't a lesser version of Monad — it's a genuinely useful, distinct stepping stone for the many real cases where independence is all that's actually needed.

TypeclassFunction is wrapped?Can later steps depend on earlier results?
Functor (haskell2-1)no — plain a -> bn/a — only one value involved
Applicativeyes — f (a -> b)no — combines independently
Monad (Chapter 3)n/a — uses >>= insteadyes — the whole point
Use f <$> a <*> b <*> c for combining independent wrapped values
This chained applicative style is genuinely idiomatic and common — reach for it whenever a plain multi-argument function needs to be applied across several separately-wrapped values.
Applicative cannot express "do this only if the first one succeeded, using its value"
This is a real, genuine limitation, not a bug — Applicative structurally has no way for one computation's result to influence which computation runs next. That's precisely the gap Chapter 3's own Monad exists to close.

Coding Challenges

Challenge 1

Use <*> to apply Just (*2) to Just 10, and separately apply Nothing (of the correct function type) to Just 10, printing both results.

📄 View solution
Challenge 2

Write a three-argument record constructor and combine three independently-Maybe-wrapped fields into one Maybe of the record using <$> and <*> chained together, testing both a fully-successful case and a case where one field is Nothing.

📄 View solution
Challenge 3

Write a short comment giving a concrete, realistic example of a task that CANNOT be expressed using Applicative alone (where a later step genuinely needs an earlier step's actual value), explaining exactly why <*> falls short for it.

📄 View solution

Chapter 2 Quick Reference

  • Applicative solves what Functor can't: applying a WRAPPED function to a wrapped value, via <*>
  • Applicative requires Functor as a superclass — every Applicative is automatically a Functor
  • pure lifts a plain value into the applicative context with minimal wrapping
  • f <$> a <*> b combines independently-wrapped values with an ordinary function — no equivalent in java2-1's/csharp2-1's own generics
  • Applicative combines INDEPENDENT computations only — no later step can depend on an earlier step's actual result
  • Next chapter: Monads — the track's central payoff, where dependent computations finally become possible