Using Haskell's ApplicativeDo to Optimize Do‑Notation
ApplicativeDo lets GHC rewrite eligible do‑blocks as applicative expressions, reducing overhead while preserving semantics.
17 Sept 2025, 08:15 UTC

ApplicativeDo in practice
When you enable the ApplicativeDo extension, GHC can rewrite a do block that only uses <- bindings and a final pure (or return) into an applicative expression using <$> and <*>. This often removes intermediate monadic binds and can reduce allocation.
Worked example
{-# LANGUAGE ApplicativeDo #-}
module Example where
import Control.Applicative ((<$>), (<*>))
-- Without ApplicativeDo the desugaring uses >>= twice.
readPair :: IO (String, String)
readPair = do
first <- getLine
second <- getLine
pure (first, second)
With ApplicativeDo GHC produces code equivalent to:
readPair = (\x y -> (x, y)) <$> getLine <*> getLine
You can verify the transformation by compiling with optimisation and dumping the simplified core:
ghc -XApplicativeDo -O2 -ddump-simpl -dsuppress-all Example.hs
The core will show the applicative form; without the extension you would see nested >= calls.
Limits of the transformation
- The block must not contain pattern matches that can fail, guards, or any use of
>=that depends on the monadic context beyond sequencing. - The final expression must be a plain
pure(orreturn) value; if you usefailor a monadic function that cannot be expressed applicatively, GHC falls back to standard monadic desugaring. - If the
doblock mixes applicative‑compatible and incompatible parts, only the compatible prefix may be rewritten, which can lead to unexpected code.
Common mistakes
- Assuming
ApplicativeDoworks for anydoblock. Adding a pattern match likeJust x <- maybeValuedisables the extension for that block, and you may see no performance gain. - Enabling the extension globally with a top‑level
{-# LANGUAGE ApplicativeDo #-}pragma can affect debugging information; stack traces may differ because the core structure changes. - Relying on the extension for correctness: the transformation is semantics‑preserving only when the preconditions hold. If you accidentally depend on monadic behaviour (e.g., custom
>=that logs), the optimisation could change observable behaviour.
Practical verification
To check that the optimisation is active:
- Compile the module with
-XApplicativeDo -O2. - Run the program and compare its output to a version compiled without the extension (same
-O2level). The results must be identical. - Optionally, use a benchmarking library such as
criterionto measure allocation and runtime; the applicative version often shows lower allocation for suitable patterns.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.