8

我花了半天时间试图弄清楚如何使用 EitherT 来处理我的代码中的错误。

我已经定义了一个这样的变压器堆栈。

-- Stuff Monad

data StuffConfig = StuffConfig {
  appId     :: T.Text,
  appSecret :: T.Text
}

data StuffState = StuffState {
  stateToken :: Maybe Token,
  stateTime  :: POSIXTime
}

newtype Stuff a = Stuff {
  runStuff :: (ReaderT StuffConfig (StateT StuffState (EitherT T.Text IO))) a
} deriving (Monad, Functor, Applicative, 
            MonadIO, 
            MonadReader StuffConfig,
            MonadState StuffState
            )



askStuff :: StuffConfig -> Stuff a -> IO (Either T.Text a)
askStuff config a = do
  t <- getPOSIXTime 
  runEitherT (evalStateT (runReaderT (runStuff a) config) (StuffState Nothing t))

只要我只使用ReaderTandStateT函数,它就可以很好地工作。我的印象是现在我应该能够写出这样的东西:

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when s == "left" $ left "breaking out"
  "right"

更重要的是从包中捕获Either应该可能的返回值:hoistEithererrors

faultyLookup :: Map -> String -> Stuff String
faultyLookup m k = do
  hoistEither $ lookup k m

我阅读了关于 monad 转换器的真实世界的 haskell章节并摆弄了lift. 但是我无法进行任何类型检查。

4

2 回答 2

9

您不能直接使用leftandhoistEither函数的原因是与StateT包不同,ReaderT包不提供类似于or的类型类。mtleitherMonadReaderMonadState

上述类型类透明地处理 monad 堆栈中的提升,但是对于EitherT,您必须自己进行提升(或编写MonadEither类似于MonadReaderet al 的类型类)。

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when (s == "left") $ Stuff $ lift $ lift $ left "breaking out"
  return "right"

首先,您需要应用Stuff包装器,然后liftReaderT变压器上,然后lift再在StateT变压器上。

您可能想为自己编写实用程序函数,例如

stuffLeft :: T.Text -> Stuff a
stuffLeft = Stuff . lift . lift . left

然后你可以像这样简单地使用它:

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when (s == "left") $ stuffLeft "breaking out"
  return "right"

或者,如果您为 定义实例,您可以使用Control.Monad.Errorfrom 。mtlErrorText

instance Error T.Text where
  strMsg = T.pack

现在您可以更改Stuff实现的定义lefthoistEither如下所示:

newtype Stuff a = Stuff {
  runStuff :: (ReaderT StuffConfig (StateT StuffState (ErrorT T.Text IO))) a
} deriving (Monad, Functor, Applicative,
            MonadIO,
            MonadReader StuffConfig,
            MonadState StuffState,
            MonadError T.Text
            )

left :: T.Text -> Stuff a
left = throwError

hoistEither :: Either T.Text a -> Stuff a
hoistEither = Stuff . lift . lift . ErrorT . return

有了这个您的原始faultyFunction类型检查,无需任何手动提升。

您还可以为(使用from )的任何实例编写通用实现left并适用于:hoistEitherMonadErroreitherData.Either

left :: MonadError e m => e -> m a
left = throwError

hoistEither :: MonadError e m => Either e a -> m a
hoistEither = either throwError return
于 2013-01-20T20:53:44.887 回答
2

只是添加到shang的答案:MonadError基本上是对应的类型类EitherT。您可以添加它的实例EitherT(由于某种原因它在库中被注释掉了either):

import Control.Monad.Trans.Either
  hiding (left, right, hoistEither)

instance Monad m => MonadError e (EitherT e m) where
  throwError = EitherT . return . Left
  EitherT m `catchError` h = EitherT $ m >>= \a -> case a of
    Left  l -> runEitherT (h l)
    Right r -> return (Right r)

然后,定义您自己的方法,这些方法可以概括为MonadError

left :: MonadError e m => e -> m a
left = throwError
{-# INLINE left #-}

right :: MonadError e m => a -> m a
right = return
{-# INLINE right #-}

hoistEither :: MonadError e m => Either e a -> m a
hoistEither (Left a)  = throwError a
hoistEither (Right e) = return e
{-# INLINE hoistEither #-}

现在您可以执行以下操作:

import qualified Data.Map as Map

newtype Stuff a = Stuff {
  runStuff :: (ReaderT Int (StateT Char (EitherT T.Text IO))) a
} deriving (Monad, Functor,
            MonadReader Int,
            MonadError T.Text, -- <--- MonadError instance
            MonadState Char
            )


faultyLookup :: (Ord k) => Map.Map k a -> k -> Stuff a
faultyLookup m k =
  maybe (left $ T.pack "Lookup error") right $ Map.lookup k m

或将其概括为

faultyLookup :: (MonadError T.Text m, Ord k) => Map.Map k a -> k -> m a
faultyLookup m k =
  maybe (left $ T.pack "Lookup error") right $ Map.lookup k m
于 2013-01-22T08:34:50.193 回答