EitherT如何运作?

时间:2013-01-20 20:10:06

标签: error-handling monad-transformers haskell either

我花了一半时间试图弄清楚如何使用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))

只要我只使用ReaderTStateT函数,这个效果就会很好。我的印象是,现在我应该可以这样写:

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

更重要的是从Either包中获取hoistEither应该可以获得的errors返回值:

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

我阅读了关于monad变换器的现实世界haskell 章节,并在lift中徘徊。但我无法得到任何东西。

2 个答案:

答案 0 :(得分:9)

您不能直接使用lefthoistEither函数的原因与mtl包中的StateTReaderT不同,{ {3}}包不提供类似于MonadReaderMonadState的类型类。

前面提到的类型类可以透明地处理monad堆栈中的提升,但是对于EitherT,你必须自己解除(或写一个类似于MonadEither等的MonadReader类型类。 )。

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

首先,您需要在Stuff变换器上应用lift包装,然后ReaderT,然后再在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"

或者,如果您为mtl定义Error个实例,则可以使用Text中的either

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类型检查,无需任何手动提升。

您还可以编写lefthoistEither的通用实现,这些实现适用于MonadError的任何实例(使用either中的Data.Either):

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

hoistEither :: MonadError e m => Either e a -> m a
hoistEither = either throwError return

答案 1 :(得分: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