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

Error Handling in Haskell

Learn how Haskell represents failure explicitly using Maybe, Either, and exceptions, and when to reach for each approach.

Practical HaskellIntermediate10 min readJul 10, 2026
Analogies

Maybe and Either for Explicit Failure

Haskell represents optional or absent values with Maybe a (Nothing or Just a), and failures that need an explanation with Either e a (Left e for failure with a reason, Right a for success). Because both possibilities are encoded directly in the function's type signature, the compiler forces every caller to pattern-match or otherwise handle the failure case -- unlike exceptions, which are completely invisible in a type signature like readFile :: FilePath -> IO String.

🏏

Cricket analogy: A function returning Maybe Int instead of just Int is like a scorecard that explicitly has a 'did not bat' entry rather than silently listing a 0, forcing anyone reading it to distinguish 'scored zero' from 'never got the chance,' exactly the distinction between Just 0 and Nothing.

Chaining Failure with Monads

Because Maybe and Either e are both monads, you can chain a sequence of steps that might fail using do-notation or >>=, and the chain automatically short-circuits at the first Nothing or Left, skipping every remaining step. For application code that needs to combine this kind of explicit, short-circuiting error handling with IO, the ExceptT e IO a monad transformer (and MaybeT for the Maybe case) layers Either-style failure on top of another monad so you can fail explicitly while still performing IO.

🏏

Cricket analogy: Chaining Either String computations with do-notation so the first Left short-circuits the rest is like a bowling review process where if the front-foot no-ball check fails first, the umpire doesn't even bother checking the LBW angle afterward.

haskell
import Control.Exception (catch, SomeException, try)

data ValidationError = EmptyName | InvalidAge Int
  deriving Show

validateUser :: String -> Int -> Either ValidationError (String, Int)
validateUser name age = do
  n <- if null name then Left EmptyName else Right name
  a <- if age < 0 || age > 130 then Left (InvalidAge age) else Right age
  Right (n, a)

main :: IO ()
main = do
  print (validateUser "Ada" 36)      -- Right ("Ada",36)
  print (validateUser "" 36)         -- Left EmptyName

  result <- try (readFile "missing.txt") :: IO (Either SomeException String)
  case result of
    Left err      -> putStrLn ("Failed to read file: " ++ show err)
    Right content -> putStrLn content

Runtime Exceptions: throw, catch, and SomeException

Control.Exception provides throwIO, catch, and try for genuine runtime and IO-level failures like a missing file or a network timeout, built on an extensible Exception typeclass so any exception type can be caught generically as SomeException or specifically by its concrete type. Exceptions are best reserved for truly exceptional, IO-boundary conditions -- not for routine control flow like validating a form field, which is exactly what Either was designed for.

🏏

Cricket analogy: catch handling a SomeException thrown by a truly unexpected failure is like a ground's contingency plan for a power outage during a floodlit match -- a rare, exceptional event handled with a special recovery procedure, not part of the normal over-by-over flow.

Partial functions like head, tail, and fromJust throw an unchecked, pure runtime exception (Prelude.head: empty list) with no trace in the type signature -- head :: [a] -> a looks total but isn't, so a caller has no compiler-enforced hint they need to handle the empty-list case; prefer Data.List.NonEmpty or pattern matching with an explicit Maybe return instead.

Choosing Between Maybe, Either, and Exceptions

A practical rule of thumb: use Maybe for simple, self-explanatory absence where no extra context is needed (does this list have a head?); use Either when a caller genuinely needs to know *why* something failed, especially for expected business-rule failures like validation errors; and reserve real exceptions for IO-boundary conditions that are truly unexpected and where recovery, if any, happens at a much higher level -- disk failures, network timeouts, or corrupted external data.

🏏

Cricket analogy: Choosing Maybe for a simple lookup like 'did this batter score a century' versus Either with a detailed reason for 'why was this delivery called a no-ball' versus a true exception only for something like the stadium's power failing mid-over mirrors matching the right tool to how much explanation the failure deserves.

Control.Exception.bracket acquire release use guarantees release runs even if use throws an exception, making it the standard pattern for exception-safe resource management in Haskell -- for example, bracket (openFile p ReadMode) hClose $ \h -> ... always closes the handle, whether or not reading throws.

  • Maybe a (Nothing/Just) represents simple absence; Either e a (Left e/Right a) represents failure carrying an explanatory error value.
  • Both encode failure in the type signature, forcing callers to handle it -- unlike exceptions, which are invisible to the type checker.
  • Do-notation and >>= short-circuit a chain of Maybe/Either computations on the first Nothing/Left, skipping the remaining steps.
  • ExceptT/MaybeT monad transformers let you combine Maybe/Either-style short-circuiting with IO or other monads.
  • Control.Exception provides throwIO, catch, and try for genuine runtime/IO-level failures via the extensible Exception typeclass and SomeException.
  • Partial functions like head and fromJust throw untyped runtime exceptions that bypass the compiler's guarantees -- avoid them in production code paths.
  • bracket guarantees resource cleanup even when an exception is thrown, and is the standard pattern for exception-safe resource handling.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#HaskellStudyNotes#ErrorHandlingInHaskell#Error#Handling#Haskell#Maybe#ErrorHandling#StudyNotes#SkillVeris