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:
.map, named. The function is plain; the value is boxed..map alone can't finish — this is the tool that does.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.
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].fmapA 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.
A real Functor has to behave. Two laws say "mapping touches the contents, never the box":
fmap id = id — mapping the do-nothing function changes nothing. A [1,2,3] stays a three-element list; a Just stays a Just.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.
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.fmap runs outSo 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.
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.<*> + pureApplicative 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.
The payoff is combining independent effects in one clean expression. A few places it earns its keep:
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.)liftA2 f a b is just f <$> a <*> b with a name — apply a two-arg function across two boxes.(+) <$> [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.
These two are the first rungs of a three-rung ladder, each strictly more powerful than the last:
<$>.<*>, pure.>>=, 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.
Side by side: the .map, .mapN, and for-comprehension you'd write in Scala, and their <$> / <*> counterparts in Haskell.
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.