Skip to content
Either Monad
step 1/7

Reading — step 1 of 7

Learn

~3 min readPractical Haskell

Maybe says "value or nothing." Either says "value or REASON for missing." Real World Haskell treats Either as the standard error-handling type for pure code.

The Either type

data Either a b = Left a | Right b

By convention:

  • Left carries an ERROR (often a String or custom error type)
  • Right carries a SUCCESS value

Mnemonic: "Right is right (correct)."

Basic usage

safeDivide :: Int -> Int -> Either String Int
safeDivide _ 0 = Left "divide by zero"
safeDivide a b = Right (a `div` b)

safeDivide 10 2     -- Right 5
safeDivide 10 0     -- Left "divide by zero"

Pattern matching

case safeDivide x y of
    Right result -> putStrLn ("got " ++ show result)
    Left err     -> putStrLn ("error: " ++ err)

Either as a Monad — chaining

The killer feature: Either e is a Monad. Failures short-circuit:

parseAndDivide :: String -> String -> Either String Int
parseAndDivide a b = do
    x <- readEither a       -- Either String Int
    y <- readEither b
    if y == 0
        then Left "divide by zero"
        else Right (x `div` y)

readEither (from Text.Read) returns Either String Int. The do block threads the Either monad — first Left short-circuits the whole computation.

parseAndDivide "10" "2"      -- Right 5
parseAndDivide "abc" "2"     -- Left "Prelude.read: no parse"
parseAndDivide "10" "0"      -- Left "divide by zero"

Why Either over Maybe?

findUser :: Int -> Maybe User
-- caller knows it failed but not why

findUser :: Int -> Either String User
-- caller gets a meaningful error message

Use Maybe when the failure mode is obvious or there's only one. Use Either when there are multiple ways to fail and the caller needs to discriminate.

Either with custom error types

data UserError
    = NotFound
    | Unauthorized
    | InvalidEmail
    deriving (Show)

getUser :: Int -> Either UserError User
getUser id = ...

case getUser 42 of
    Right user        -> ...
    Left NotFound     -> log "missing"
    Left Unauthorized -> log "403"
    Left InvalidEmail -> log "bad email format"

Using an ADT for errors lets you pattern-match on specific error types — much better than parsing strings.

Combining Eithers

import Data.Either (lefts, rights, partitionEithers)

results :: [Either String Int]
results = [Right 1, Left "err1", Right 2, Left "err2", Right 3]

lefts results              -- ["err1", "err2"]
rights results             -- [1, 2, 3]
partitionEithers results   -- (["err1", "err2"], [1, 2, 3])

Useful for collecting all errors AND all successes from a batch operation.

ExceptT — Monad transformer for IO + Either

For IO that can fail with typed errors:

import Control.Monad.Except

fetchAndProcess :: ExceptT String IO Result
fetchAndProcess = do
    raw <- fetch                           -- IO String
    parsed <- liftEither (parse raw)        -- Either String Result
    return parsed

-- Run:
result <- runExceptT fetchAndProcess        -- IO (Either String Result)

Monad transformers like ExceptT are covered in Advanced. They let you stack effects like Either-on-top-of-IO without manual plumbing.

Common mistakes

  • Using String as the error type forever — strings are unstructured. Define an ADT (data MyError = ...) for proper error matching.
  • Mixing Maybe and Either in one chain — they're different monads. Use maybeToEither or pick one.
  • Forgetting which side is success — Right is right (success). Left is error.
  • Pattern-match exhaustiveness ignored — without a Left case in your match, the unmatched Left will throw at runtime.
  • Reaching for Either when Maybe suffices — overhead of an error message you'll never use.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…