Reading — step 1 of 7
Learn
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:
Leftcarries an ERROR (often a String or custom error type)Rightcarries 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
Stringas the error type forever — strings are unstructured. Define an ADT (data MyError = ...) for proper error matching. - Mixing
MaybeandEitherin one chain — they're different monads. UsemaybeToEitheror 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
EitherwhenMaybesuffices — 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…