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?

Cactus
  • 27,075
  • 9
  • 69
  • 149
romeovs
  • 5,785
  • 9
  • 43
  • 74
  • 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`. – ibotty Jan 04 '16 at 10:37
  • Your `liftEither` sounds like the right answer to me (or Cactus's answer about generalizing the type of `parseUser`. – Jonathan Cast Jun 06 '18 at 21:40

3 Answers3

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.

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 2
    Note that for the contraint `MonadError String m` ghc needs the `FlexibleContexts` extension (see http://stackoverflow.com/a/22795830/905686). – user905686 Mar 03 '17 at 11:26
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
Xiaokui Shu
  • 422
  • 2
  • 10
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.

Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196