λ Scala → Haskell · ch.5 · functor & applicative
🎁 Chapter 5 · Reaching inside the box

map over anything
in a box.

You already call .map on Option, List, and Future. Functor is just that idea, named — and Applicative is the next step: applying a function that is itself inside a box.

Every day you work with values wrapped in something — an Option that might be empty, a List of many, a Future still computing. You rarely tear the wrapper off by hand. You reach in with .map, transform the inside, and hand the wrapper back. Haskell names that pattern and pushes it one rung further.

Two ideas, two rungs:

  • Functor — "map over the contents of a box." Your .map, named. The function is plain; the value is boxed.
  • Applicative — "apply a boxed function to a boxed value." When the function itself got trapped in a box, .map alone can't finish — this is the tool that does.

1A value in a box

Think about what Option, List, Either, and Future have in common. Each one is a box around a value you don't want to unwrap by hand. A Some(3) holds a 3 but might've been None; a List(1,2,3) holds three values; a Future[Int] holds an Int that isn't ready yet.

When you want to add one to the number inside, you don't pattern-match the empty case, pull the value out, transform it, and re-wrap. You write opt.map(_ + 1) and let the box handle its own shape. None.map(_ + 1) stays None; you never had to think about it.

That single move — reach in, transform, hand the box back unchanged in shape — is the whole idea. Haskell gives the boxes that support it a name: Functor.

💡
A "box" here is any type with one type parameter — Maybe a, [a], Either e a. Haskell writes the box as f and the value inside as a, so f a means "an a in a box f." That's exactly Scala's F[A].

2Functor = fmap

A Functor is any box you can map over. The operation is fmap, and its type says it all:

fmap :: (a -> b) -> f a -> f b

Read it plainly: give fmap a plain function a -> b and a box of a, and it hands back a box of b — same box shape, new contents. That is Scala's map signature with the arguments flipped: def map[A,B](f: A => B): F[A] => F[B].

The everyday spelling is the operator <$> — pronounce it "fmap." It reads like function application with a dollar of "into the box":

(+1) <$> Just 3   -- => Just 4
(+1) <$> Nothing  -- => Nothing
(+1) <$> [1,2,3]  -- => [2,3,4]

Notice Nothing stays Nothing. The function never runs, because there's nothing inside to run it on — exactly like None.map. Pick a box and a function below and watch the function reach inside.

Interactive · fmap visualizer Pick a box and a function · watch it reach inside
Box: Function:
pick and run

3The laws, briefly

A real Functor has to behave. Two laws say "mapping touches the contents, never the box":

  • Identity. fmap id = id — mapping the do-nothing function changes nothing. A [1,2,3] stays a three-element list; a Just stays a Just.
  • Composition. fmap (f . g) = fmap f . fmap g — mapping twice equals mapping once with the composed function. No surprises, no extra effects sneaking in between.

The point for you as a programmer: mapping never changes the box's shape, only its contents. A map over a 3-element list always yields a 3-element list; it can't quietly drop, duplicate, or reorder. Scala expects the same of a lawful map — these laws just make the contract explicit.

⚖️
The compiler doesn't check these laws — they're a promise you make when you write an instance Functor. Break them and code that relies on fmap behaving will misbehave, the same way a map that secretly reorders a list would wreck downstream code in Scala.

4Where fmap runs out

So far the function was plain and one value was boxed. But what if you want to combine two boxed values — add a Just 3 and a Just 4? You reach for fmap with a two-argument function and hit a wall:

(+) <$> Just 3  -- => Just (3+)

Look at what happened. (+) takes two arguments; fmap fed it just the first one (the 3 inside the box), and because functions are curried, you got back a partially applied function(3+) — now sitting inside the box.

So now you hold two boxes: Just (3+), a function in a box, and Just 4, an argument in a box. To finish you need to apply the boxed function to the boxed value. And fmap can't do that — its function argument has to be plain, not boxed. You've run out of road.

⚠️
This is the exact gap Applicative fills. fmap handles one boxed value and a plain function. The moment the function — or any remaining argument — is itself in a box, you need the next tool.

5Applicative = <*> + pure

Applicative adds exactly the operator the last section was missing. <*> (say "ap") applies a boxed function to a boxed value:

(<*>) :: f (a -> b) -> f a -> f b

Compare it to fmap: the only change is that the function is now in a box too — f (a -> b) instead of plain a -> b. That's the whole upgrade. Chain it after <$> and the two-argument case just works:

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

Read left to right: (+) <$> Just 3 puts (3+) in a box, then <*> Just 4 applies that boxed function to Just 4, giving Just 7. If any box is Nothing, the whole thing is Nothing.

The other half of Applicative is pure — it puts a plain value into the box: pure 5 :: Maybe Int is Just 5. That's Scala's Option(5) / Applicative[F].pure(5). And the whole pattern — combining several boxed values with one function — is Scala's (a, b).mapN(_ + _). Step through it below.

Interactive · applicative stepper Step through pure (+) <*> Just 3 <*> Just 4

6Why it matters

The payoff is combining independent effects in one clean expression. A few places it earns its keep:

  • Form validation that accumulates errors. With Scala's Validated / cats, you write (validName, validAge, validEmail).mapN(User) and get back all the errors at once, not just the first. That's Applicative: each check is independent, so all of them run. (A monadic chain would stop at the first failure.)
  • Lifting plain functions over effects. liftA2 f a b is just f <$> a <*> b with a name — apply a two-arg function across two boxes.
  • Every combination. For lists, (+) <$> [1,2] <*> [10,20] gives the cartesian product [11,21,12,22] — every left paired with every right. The "box" is nondeterminism.

The thread through all three: the pieces don't depend on each other. You're not feeding one result into the next; you're running them side by side and combining the answers. That independence is the signature of Applicative — and the line where Monad takes over.

Interactive · list <*> grid Every left paired with every right
xs: op: ys:

7The ladder

These two are the first rungs of a three-rung ladder, each strictly more powerful than the last:

  • Functor — map a plain function over one box. <$>.
  • Applicative — apply a boxed function over boxes, combining independent effects. <*>, pure.
  • Monad — let a later step depend on an earlier step's unwrapped value. >>=, do.

Every Applicative is a Functor; every Monad is an Applicative. Each adds power: Functor can't combine two boxes, Applicative can — but only when the steps are independent. When a later step needs to see the result of an earlier one, you climb the last rung. That's the next chapter.

8The same idea, in both languages

Side by side: the .map, .mapN, and for-comprehension you'd write in Scala, and their <$> / <*> counterparts in Haskell.

9Check yourself

3 questions · instant feedback 0 / 3

10Where this goes next

You can map over any box and apply boxed functions across independent boxes. The one thing you still can't do is let a later step depend on an earlier step's value — look up a key, then use what you found to look up the next. That dependency is what Monad buys you, and a Scala for-comprehension is already that shape. Next chapter: flatMap, >>=, and do.

🔗
Up next · Chapter 6
Monads & do-notation
flatMap, and how a for-comprehension becomes do.