Поднимите Либо до ExceptT автоматически

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

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.

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


person romeovs    schedule 04.01.2016    source источник
comment
А как насчет ExceptT . return? ExceptT = ExceptT (m (Either e a)), поэтому return приводит вас к IO (Either String User), а ExceptT (как конструктор/функция) к ExceptT String IO User.   -  person ibotty    schedule 04.01.2016
comment
Ваш liftEither звучит как правильный ответ для меня (или ответ Кактуса об обобщении типа parseUser.   -  person Jonathan Cast    schedule 07.06.2018


Ответы (3)


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

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

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

Способ сделать это состоит в том, чтобы заменить Right на return и Left на throwError в определении parseUser.

person Cactus    schedule 04.01.2016
comment
Обратите внимание, что для ограничения MonadError String m ghc требуется расширение FlexibleContexts (см. stackoverflow.com/a/22795830/905686). - person user905686; 03.03.2017

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

tryRight :: Monad m => Either e a -> ExceptT e m a
person Xiaokui Shu    schedule 06.06.2018