Let's say I have this (arguably mislead) piece of code laying around:
import System.Environment (getArgs)
import Control.Monad.Except
parseArgs :: ExceptT String IO User
parseArgs =
do
args <- lift getArgs
case safeHead args of
Just admin -> parseUser admin
Nothing -> throwError "No admin specified"
parseUser :: String -> Either String User
-- implementation elided
safeHead :: [a] -> Maybe a
-- implementation elided
main =
do
r <- runExceptT parseArgs
case r of
Left err -> putStrLn $ "ERROR: " ++ err
Right res -> print res
ghc
gives me the following error:
Couldn't match expected type ‘ExceptT String IO User’
with actual type ‘Either String User’
In the expression: parseUser admin
In a case alternative: Just admin -> parseUser admin
What's the most standard way of lifting an Either
into an ExceptT
?
I feel there must be some way since Either String
is an instance of MonadError
.
I wrote my own lifting function:
liftEither :: (Monad m, MonadError a (Either a)) => Either a b -> ExceptT a m b
liftEither = either throwError return
But to me this still feels wrong since I'm already working inside the
ExceptT
monad transformer.
What am I doing wrong here? Should I structure my code differently?
You can generalise parseUser
's type to
parseUser :: (MonadError String m) => String -> m User
and then it would work both at m ~ Either String
and at m ~ ExceptT String m'
(if only Monad m'
) without any manual lifting necessary.
The way to do it is to basically replace Right
with return
and Left
with throwError
in parseUser
's definition.
If you are using transformers
instead of mtl
, then you can use tryRight
from Control.Error.Safe
.
tryRight :: Monad m => Either e a -> ExceptT e m a
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