lift Either to ExceptT automatically
Asked Answered
C

3

10

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?

Conn answered 4/1, 2016 at 9:55 Comment(2)
What about ExceptT . return? ExceptT = ExceptT (m (Either e a)), so return gets you to IO (Either String User) and ExceptT (as constructor/function) to ExceptT String IO User.Lubet
Your liftEither sounds like the right answer to me (or Cactus's answer about generalizing the type of parseUser.Roughdry
M
10

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.

Mammillate answered 4/1, 2016 at 12:3 Comment(1)
Note that for the contraint MonadError String m ghc needs the FlexibleContexts extension (see https://mcmap.net/q/861277/-non-type-variable-argument-in-the-constraint-monaderror-failure-m).Southwestward
T
3

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
Taskwork answered 6/6, 2018 at 21:36 Comment(0)
I
0

You can use except in transformers (Control.Monad.Trans.Except):

except :: Monad m => Either e a -> ExceptT e m a

It is defined as ibotty suggested in a comment.

It's a bit different from your liftEither, since it doesn't MonadError. You can use it anywhere where ExceptT is applicable.

BTW, there is already a liftEither, which is different:

liftEither :: MonadError e m => Either e a -> m a.

I don't think this one is useful for you here.

Intensive answered 3/8, 2021 at 15:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.