自动将 Either 提升到 ExceptT

lift Either to ExceptT automatically

假设我有这段(可以说是误导的)代码:

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 StringMonadError.

的一个实例

我写了自己的提升函数:

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

但对我来说这仍然感觉不对,因为我已经在里面工作了 ExceptT 单子变换器。

我在这里做错了什么?我应该以不同的方式构建我的代码吗?

您可以将 parseUser 的类型概括为

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

然后它将在 m ~ Either Stringm ~ ExceptT String m'(如果只有 Monad m')都可以工作,而无需任何手动提升。

实现的方法基本上是在parseUser的定义中将Right替换为return,将Left替换为throwError

如果您使用 transformers 而不是 mtl,那么您可以使用 Control.Error.Safe 中的 tryRight

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

您可以在 transformers (Control.Monad.Trans.Except) 中使用 except:

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

定义为评论中建议的ibotty

它与您的 liftEither 有点不同,因为它 MonadError。您可以在适用 ExceptT 的任何地方使用它。

顺便说一句,已经有一个liftEither,不同的是:

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

我觉得这个对你没用。