Use the errors
library, which was created precisely for this purpose. It allows you to unify diverse failing computations to agree on a common error-handling mechanism.
For example, let's say that you have two computations, one of which fails using Maybe
, and the other of which fails using Either String
:
safeHead :: [a] -> Maybe a
safeHead as = case as of
[] -> Nothing
a:_ -> Just a
safeDivide :: Double -> Double -> Either String Double
safeDivide x y = if y == 0 then Left "Divide by zero" else Right (x / y)
There are two ways we can make these two functions agree on the same error-handling mechanism. The first way is to convert the Either
function to a Maybe
by suppressing the String
error. This is what the hush
function does:
-- Provided by the `errors` package
hush :: Either e a -> Maybe a
example1 :: [Double] -> Maybe Double
example1 xs = do
x <- safeHead xs
hush (safeDivide 4 x)
Alternatively, we can annotate the Maybe
computation with a descriptive String
error message if it fails. This is what the note
function does:
-- Also provided by the `errors` package:
note :: e -> Maybe a -> Either e a
example2 :: [Double] -> Either String Double
example2 xs = do
x <- note "Empty list" (safeHead xs)
safeDivide 4 x
The errors
package also includes conversion functions for MaybeT
and EitherT
, too. This lets you unify all your error-handling machinery to use the target monad of your choice.
Using your stair
example, you would simplify this by getting them to agree on MaybeT
:
stair = void $ runMaybeT $ do
s <- lift getLine
s4 <- hoistMaybe $ do
s1 <- stepMaybe s
s2 <- hush $ stepEither s1
s3 <- headMay $ stepList s2
stepMaybe s3
lift $ print s4