Either and recoverable errors
| Two sides | |
| A parsing example | |
| either and chain | |
| Either or exceptions |
Two sides
Maybe says that something failed but not why. Either carries a reason on the left and the result on the right:
data Either a b = Left a | Right b
half :: Int -> Either String Int
half n
| even n = Right (n `div` 2)
| otherwise = Left "odd number"
By convention Left is the failure and Right is the success, which is why mapping over an Either touches only the right side.
A parsing example
Reading a number from text is the classic fallible step. With readMaybe the failure is explicit, and the message can say what was wrong:
import Text.Read (readMaybe)
parseAge :: String -> Either String Int
parseAge s = case readMaybe s of
Nothing -> Left ("not a number: " ++ s)
Just n
| n < 0 -> Left "negative age"
| otherwise -> Right n
The signature promises a number or a message, and the caller cannot ignore the second possibility without writing a branch for it.
either and chain
The either function collapses both sides into one type, and >>= chains fallible steps while keeping the first error:
either length id (Right "abc") -- 3
either length id (Left "bad") -- 3
chain s = parseAge s >>= half
-- chain "42" == Right 21
-- chain "x" == Left "not a number: x"
Note that half takes an Int and returns Either String Int, while parseAge already returns that type. The operator is what makes the two fit together.
Either or exceptions
An exception is invisible in the signature and can escape from any call. An Either is in the type, so the compiler knows about it and the caller must decide. Lesson 46 covers the cases where exceptions are still the right tool.
-- invisible failure
readAge :: String -> Int
-- failure is part of the contract
readAge :: String -> Either String Int
Prefer the second in library code and at module boundaries. Keep the
first for a scratch script where a crash is an acceptable answer.
Next:
data: product types
Article author: Arthur Isaev