Скажем, у меня есть этот (возможно, вводящий в заблуждение) фрагмент кода:

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 дает мне следующую ошибку:

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

Какой самый стандартный способ поднять Either в ExceptT? Я чувствую, что должен быть какой-то способ, поскольку Either String является экземпляром MonadError.

Я написал свою подъемную функцию:

liftEither :: (Monad m, MonadError a (Either a)) => Either a b -> ExceptT a m b
liftEither = either throwError return

Но мне это все еще кажется неправильным, так как я уже работаю внутри Преобразователь монад ExceptT.

Что я здесь делаю не так? Следует ли мне по-другому структурировать свой код?

7
romeovs 4 Янв 2016 в 12:55

2 ответа

Лучший ответ

Вы можете обобщить тип parseUser на

parseUser :: (MonadError String m) => String -> m User 

И тогда он будет работать как в m ~ Either String, так и в m ~ ExceptT String m' (если только Monad m') без необходимости ручного подъема.

Для этого нужно просто заменить Right на return и Left на throwError в определении parseUser.

8
Cactus 4 Янв 2016 в 12:03

Если вы используете transformers вместо mtl, вы можете использовать tryRight из Control.Error.Safe.

tryRight :: Monad m => Either e a -> ExceptT e m a
1
Xiaokui Shu 10 Июн 2018 в 15:34