100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Applicatives Explained

Learn how the Applicative type class extends Functor with pure and <*>, letting you combine several independent effectful values -- Maybe validations, list combinations, or IO actions -- with a plain function.

Functional AbstractionsIntermediate9 min readJul 10, 2026
Analogies

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.

haskell
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)      -- Nothing

The 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

Was this page helpful?

Topics covered

#Programming#HaskellStudyNotes#ApplicativesExplained#Applicatives#Explained#Functor#Applicative#StudyNotes#SkillVeris#ExamPrep