Home > Net >  Is there a cleaner way of converting an Either to an ExceptT?
Is there a cleaner way of converting an Either to an ExceptT?

Time:05-07

I have some functions which return Either values and I'd like to use them in an IO do block with what I think is called "short circuit" behaviour so that any Left values cause the rest of the items on the IO block to be bypassed. The best I've come up with so far looks something like this (an artificial example which illustrates what I'm trying to do):

import Control.Monad.Except

main :: IO ()
main = handleErrors <=< runExceptT $ do
  liftIO $ putStrLn "Starting..."
  let n = 6
  n' <- ExceptT . return . f1 $ n
  liftIO $ putStrLn "First check complete."
  n'' <- ExceptT . return . f2 $ n'
  liftIO $ putStrLn "Second check complete."
  liftIO $ putStrLn $ "Done: "    show n''

handleErrors :: Either String () -> IO ()
handleErrors (Left err) = putStrLn $ "*** ERROR: "    err
handleErrors (Right _) = return ()

f1 :: Integer -> Either String Integer
f1 n
  | n < 5 = Left "Too small"
  | otherwise = Right n


f2 :: Integer -> Either String Integer
f2 n
  | n == 10 = Left "Don't want 10"
  | otherwise = Right n

The ExceptT . return looks clumsy - is there a better way? I don't want to change the functions themselves to return ExceptT values.

CodePudding user response:

If I'm reading the types right, ExceptT . return does the same thing as liftEither. (It has a mildly more complicated implementation in order to work with any instance of MonadError)

CodePudding user response:

If you have control of f1 and f2, then one way would be to generalize them:

f1 :: MonadError String m => Integer -> m Integer
f1 n | n < 5 = throwError "Too small" | otherwise = pure n

f2 :: MonadError String m => Integer -> m Integer
f2 n | n == 10 = throwError "Don't want 10" | otherwise = pure n

Actually, I'd be tempted to even abstract this pattern:

avoiding :: MonadError e m => (a -> Bool) -> e -> a -> m a
avoiding p e a = if p a then throwError e else pure a

f1 = avoiding (<5) "Too small"
f2 = avoiding (==10) "Don't want 10"

Anyway, once you have the more general type, then you can use it directly either as an Either or an ExceptT.

main = ... $ do
    ...
    n' <- f1 n
    n'' <- f2 n'
    ...

If you do not have control of f1 and f2, then you might enjoy the errors package, which offers, among many other handy things, hoistEither. (This function may be available other places as well, but the errors package offers many useful things for converting between error types when you must call other people's functions.)

  • Related