Avoiding case expression ladder without monad transformers
coding-style, haskell, monad-transformers
Solution
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
Problem
In Real World Haskell monads are introduced as a way to avoid the code marching off the right of the screen by using the `Maybe` monad. However what happens when the case expressions include other monads like `Either` or `IO`, for example? I know the ladder could be avoided with monad transformers, but aren't they overkill for just an stylistic issue? Is there another idiomatic and lightweight way to deal with this? Update: Here is an example: ``` stair s = do s <- getLine case stepMaybe s of Nothing -> return () Just s1 -> case stepEither s1 of Left _ -> return () Right s2 -> case stepList s2 of [s3:_] -> case stepMaybe s3 of Nothing -> return () Just s4 -> print s4 _ -> return () ```