From Functor to Applicative
fmap lets you apply a plain function to a value inside a context, but it breaks down once the function itself needs more than one argument and each argument is independently wrapped in that context -- you can't fmap a two-argument function over two separate Maybe Int values and get a Maybe Int back with fmap alone. Applicative extends Functor with the ability to apply a function that is itself inside a context to an argument that is also inside that context.
Cricket analogy: Validating a player's batting average and bowling average separately, each possibly Nothing if data is missing, then wanting to combine both into a single all-rounder rating is exactly the gap Applicative fills -- fmap alone can't combine two independently wrapped values with a two-argument rating function.
The Applicative Type Class: pure and <*>
The class is class Functor f => Applicative f where pure :: a -> f a; (<*>) :: f (a -> b) -> f a -> f b. pure lifts an ordinary value into the applicative context with no extra effect -- pure 5 :: Maybe Int is Just 5 -- and <*> ('apply') takes a wrapped function and a wrapped argument and produces a wrapped result, threading through whatever effect the context represents, such as possible absence for Maybe or possible failure for Either.
Cricket analogy: pure lifting a plain rating into a context, like wrapping a hard-coded 'Not Rated' baseline into Just "Not Rated" with no lookup involved, mirrors an official simply declaring a fixed benchmark score with no match data needed.
data Form = Form { fname :: String, age :: Int } deriving Show
validateName :: String -> Maybe String
validateName n = if not (null n) then Just n else Nothing
validateAge :: Int -> Maybe Int
validateAge a = if a >= 0 && a < 130 then Just a else Nothing
mkForm :: String -> Int -> Maybe Form
mkForm n a = Form <$> validateName n <*> validateAge a
main :: IO ()
main = do
print (mkForm "Ada" 34) -- Just (Form {fname = "Ada", age = 34})
print (mkForm "" 34) -- NothingThe pattern f <$> a <*> b <*> c -- start with <$> for the first argument, then <*> for every argument after -- is idiomatic applicative style and reads as 'apply f to a, b, and c, each of which might fail, be absent, or carry an effect.'
Applicative for Maybe, Lists, and IO
For Maybe, <*> short-circuits to Nothing the moment either the function or the argument is Nothing, mirroring how mkForm above fails validation if any field is invalid. For lists, <*> applies every function in the left list to every value in the right list, producing the Cartesian product: [(+1),(*2)] <*> [10,20] = [11,21,20,40]. For IO, <*> runs the left action, then the right action, then applies the result of the first to the result of the second -- (+) <$> readLn <*> readLn reads two lines and adds them.
Cricket analogy: Combining validateStrikeRate name and validateEconomy name, each a Maybe Double, with AllRounder <$> validateStrikeRate name <*> validateEconomy name is like a selection committee only certifying a player as an all-rounder if both the batting and bowling numbers independently check out.
Applicative Laws and When to Reach for It
Like Functor, Applicative has laws -- identity (pure id <*> v = v), homomorphism, interchange, and composition -- that together guarantee <*> behaves predictably and doesn't sneak in extra effects. In practice, reach for Applicative whenever you need to combine several independent effectful values with a plain function and none of the later computations depend on the result of an earlier one; once a later step needs to inspect an earlier result to decide what to do next, you need Monad instead.
Cricket analogy: Applicative's independence -- batting stats and bowling stats don't need each other to be computed -- is like a scorer's table where the batting scorer and bowling scorer fill in their columns in parallel, neither waiting on the other's entry.
Newcomers are often surprised that [(+1),(*2)] <*> [10,20] returns a 4-element list, not a 2-element one -- list's Applicative instance models nondeterministic choice, applying every function to every argument, not zipping them pairwise. If you want pairwise combination, use ZipList instead.
- Applicative extends Functor with pure (lift a plain value) and <*> (apply a wrapped function to a wrapped argument).
- pure introduces a value into a context with no additional effect.
- <*> threads the context's effect (absence, failure, IO, nondeterminism) through function application.
- f <$> a <*> b <*> c is the idiomatic pattern for combining several independent effectful values.
- Maybe's <*> short-circuits on the first Nothing; list's <*> produces a Cartesian product, not a zip.
- Applicative laws (identity, homomorphism, interchange, composition) guarantee predictable behavior.
- Use Applicative when computations are independent; use Monad when a later step depends on an earlier result.
Practice what you learned
1. What is the type signature of <*>?
2. What does pure do?
3. What does [(+1),(*2)] <*> [10,20] evaluate to?
4. When should you reach for Monad instead of Applicative?
5. Which idiomatic pattern combines several independent effectful values with a plain function?
Was this page helpful?
You May Also Like
Functors Explained
Understand Haskell's Functor type class -- how fmap maps a function over a structure like Maybe, a list, or Either while preserving its shape, and the laws that guarantee it behaves predictably.
Monads Explained
Learn how the Monad type class extends Applicative with >>= (bind), letting a computation's result decide what runs next, and how do-notation, Maybe, list, and Either all build on it.
Maybe and Either
How Haskell uses the Maybe and Either types to represent optional values and recoverable errors explicitly, without null or exceptions.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics