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 ()
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
Yes there is. Use a smaller indentation setting ) Seriously though, the following should do the trick in your simple example.
import Prelude hiding (mapM_)
import Data.Foldable
import Data.Maybe
stair = do
s <- getLine
mapM_ print $
stepMaybe s >>=
either (const Nothing) Just . stepEither >>=
listToMaybe . stepList >>=
stepMaybe
For other cases the operations mapM, mapM_, forM, forM_, sequence, sequence_
of Data.Foldable
and Data.Traversable
would be the sole toolchain for you. That is assuming you have the missing instances for Either.
However I would still recommend considering transformers, since they pretty much are the standard way of dealing with composition of different monads.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With