5 min read
On this page

Applicatives

Functor lets you apply a single-argument function inside a structure. The moment you need to combine two or more wrapped values — say, build a User from three Maybe fields parsed from a request — fmap is not enough. Applicative fills that gap.

The class is also where you start to see the family relationship: every Monad is an Applicative, and every Applicative is a Functor. You almost never write Functor and Applicative instances without eventually writing Monad, but Applicative carries weight on its own — especially in parsers and in error-accumulating validation, where Monad would actively get in the way.

The typeclass

class Functor f => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

Two operations:

  • pure lifts a plain value into the structure. For Maybe, that is Just. For lists, \x -> [x]. For IO, an action that does no I/O and returns the value.
  • <*> (pronounced "ap" or "apply") takes a function inside the structure and an argument inside the structure, and produces the result inside the structure.

The minimum useful pattern is f <$> x <*> y. Read it as: apply f to the value in x and the value in y, doing whatever the structure dictates along the way.

Concrete examples

Maybe

If any of the inputs are Nothing, the whole thing is Nothing:

addM :: Maybe Int -> Maybe Int -> Maybe Int
addM mx my = (+) <$> mx <*> my

ghci> addM (Just 3) (Just 4)
Just 7
ghci> addM (Just 3) Nothing
Nothing

Three arguments? Add another <*>:

data User = User { name :: String, age :: Int, email :: String }

mkUser :: Maybe String -> Maybe Int -> Maybe String -> Maybe User
mkUser mn ma me = User <$> mn <*> ma <*> me

This is a common shape in code that decodes optional fields, like aeson's parseJSON:

instance FromJSON User where
  parseJSON = withObject "User" $ \o ->
    User <$> o .: "name"
         <*> o .: "age"
         <*> o .: "email"

o .: "name" returns a Parser String. Parser is an Applicative. The <$>/<*> chain is how you build a record from multiple parsed fields. This idiom is everywhere in Haskell — once you can read it, half the Aeson, optparse-applicative, and persistent code becomes legible.

Either

Either e short-circuits on the first Left:

ghci> (+) <$> Right 3 <*> Right 4 :: Either String Int
Right 7
ghci> (+) <$> Left "boom" <*> Right 4 :: Either String Int
Left "boom"
ghci> (+) <$> Left "first" <*> Left "second" :: Either String Int
Left "first"

Notice the last one: only the first error survives. That is fine for a parser bailing out, but disastrous for a form validator where you want to report all the problems at once. We will fix that in a moment.

Lists

Applicative for [] is the cartesian product:

ghci> (+) <$> [1, 2] <*> [10, 20]
[11, 21, 12, 22]
ghci> (,) <$> [1, 2, 3] <*> ['a', 'b']
[(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b')]

Every combination of inputs gets applied. This is liftA2 for lists.

IO

ghci> (+) <$> readLn <*> readLn :: IO Int
-- enter 3, then 4
7

Each IO action runs in order, and the final value is the function applied to both results.

Useful idioms

liftA2 and friends

When f <$> x <*> y is awkward, liftA2 reads cleaner:

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f x y = f <$> x <*> y

There is also *> and <*:

(*>) :: Applicative f => f a -> f b -> f b   -- run a, discard, return b
(<*) :: Applicative f => f a -> f b -> f a   -- run a, then b, return a's

You see these in parsers all the time:

-- megaparsec
parens :: Parser a -> Parser a
parens p = char '(' *> p <* char ')'

Run (, parse p, run ), return only what p produced.

Validation: when Either is the wrong fit

Either's applicative short-circuits. For form validation, you want every error reported. The validation package (Tony Morris, hosted on Hackage) provides exactly this:

import Data.Validation

data SignupError = EmptyName | InvalidEmail | TooYoung
  deriving Show

validateName :: String -> Validation [SignupError] String
validateName "" = Failure [EmptyName]
validateName n  = Success n

validateEmail :: String -> Validation [SignupError] String
validateEmail e
  | '@' `elem` e = Success e
  | otherwise    = Failure [InvalidEmail]

validateAge :: Int -> Validation [SignupError] Int
validateAge a
  | a >= 13   = Success a
  | otherwise = Failure [TooYoung]

mkSignup :: String -> String -> Int -> Validation [SignupError] User
mkSignup n e a =
  User <$> validateName n
       <*> validateAge a
       <*> validateEmail e

If everything is good, Success. If multiple things fail, the Failure list contains all of them:

ghci> mkSignup "" "bad" 5
Failure [EmptyName, TooYoung, InvalidEmail]

That works because Validation's <*> instance combines failures with <> instead of stopping at the first one. Crucially, Validation is not a Monad — and that is on purpose. There is no lawful Monad instance that also accumulates errors, because >>= makes the second action depend on the first's value. Once the first fails, there is no value to feed in. This is one of the rare places where being "less powerful" is the whole point.

Parsers

Parser combinator libraries — megaparsec, attoparsec, parsec, optparse-applicative — are built on Applicative (and usually Monad too). The applicative shape lets the parser inspect itself, which is how optparse-applicative generates --help output:

import Options.Applicative

data Options = Options { verbose :: Bool, file :: FilePath }

opts :: Parser Options
opts = Options
  <$> switch (long "verbose" <> short 'v')
  <*> strOption (long "file" <> short 'f' <> metavar "FILE")

If this used Monad's >>=, the parser structure would be hidden behind a function and --help could not enumerate the options. Applicative is fully introspectable; Monad is not.

The relationship to Functor and Monad

Three classes, ordered by power:

Functor       (fmap)
   ^
Applicative   (pure, <*>, fmap is derivable)
   ^
Monad         (>>=, pure)

Anything you can do with fmap you can do with <*> (it is fmap f x = pure f <*> x). Anything you can do with <*> you can do with >>=. So why not always reach for Monad?

Two reasons:

  1. Some types are Applicative but not Monad. Validation is the canonical example. Const e is another. If you write a function with a Monad constraint, it cannot be used with these types.
  2. Applicative is more analyzable. A do-block with >>= makes later actions depend on earlier results — the structure is opaque. An applicative chain is fixed at compile time. Libraries like optparse-applicative and the Free applicative exploit this.

Practical rule: write the weakest constraint that does the job. If your function does not need to inspect a result before deciding what to run next, use Applicative.

Common pitfalls

Forgetting that Either short-circuits. New users assume Either e accumulates errors because the data structure could in principle hold a list. It does not. Reach for Validation when you want accumulation.

Mixing up <$> and <*>. First operator is always <$> (the function is plain). Every operator after is <*> (the function is now wrapped). f <$> x <*> y <*> z, never f <*> x <*> y.

Assuming pure is "do nothing". It is "lift a value with no extra effect". For IO, pure x is a real IO action that just returns x. For lists, pure x = [x], not []. Confusing pure with mempty is a classic beginner trap.

Reaching for Monad when Applicative would do. A type signature forall m. Monad m => ... is harder to use than forall f. Applicative f => .... If you do not need >>=, do not require it.

Stacking applicatives by hand when traverse exists. If you have [Maybe a] and want Maybe [a], do not fold by hand — that is sequence, or more generally traverse. Same for Validation-typed lists.

Key takeaways

  • Applicative adds pure and <*> to Functor, letting you combine multiple wrapped values.
  • The pattern f <$> x <*> y <*> z is everywhere — Aeson, optparse-applicative, megaparsec, persistent.
  • Either e short-circuits; Validation e accumulates. Pick the one whose semantics match your problem.
  • Parser libraries lean on Applicative because applicative chains are statically analyzable; monadic chains are not.
  • Validation is the textbook example of a type that is Applicative but deliberately not a Monad.
  • Use the weakest typeclass constraint that gets the job done. Applicative constraints make functions work with more types than Monad constraints do.