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 () ```

Original source