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.