Error handling 它是怎么工作的?

Error handling 它是怎么工作的?,error-handling,monad-transformers,haskell,either,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

我花了半天的时间试图找出如何使用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))
更重要的是从
错误
包中捕获
返回值

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

我阅读了真实世界中关于monad transformers的haskell一章,并摆弄着
lift
。但我找不到任何要打字的东西

不能直接使用
left
righter
函数的原因是,与包中的
StateT
ReaderT
不同,包中没有提供类似于
monaderader
MonadState
的类型类

前面提到的类型类透明地处理monad堆栈中的提升,但是对于
EitherT
,您必须自己进行提升(或者编写一个
MonadEither
类型类,类似于
monaderreader
等)

首先,您需要将
Stuff
包装器应用到
ReaderT
转换器上,然后将
lift
再次应用到
StateT
转换器上

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

stuffLeft :: T.Text -> Stuff a
stuffLeft = Stuff . lift . lift . left
然后您可以像这样简单地使用它:

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when s == "left" $ left "breaking out"
  "right"
faultyFunction :: String -> Stuff String
faultyFunction s = do
  when (s == "left") $ stuffLeft "breaking out"
  return "right"
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
或者,如果为
文本
定义了
错误
实例,则可以使用from
mtl

instance Error T.Text where
  strMsg = T.pack
现在您可以更改
Stuff
implement
left
的定义,如下所示:

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when s == "left" $ left "breaking out"
  "right"
faultyFunction :: String -> Stuff String
faultyFunction s = do
  when (s == "left") $ stuffLeft "breaking out"
  return "right"
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
类型检查无需任何手动提升

您还可以为
left
righteror
编写通用实现,它们适用于
MonadError
的任何实例(使用
数据中的
):


为了补充shang的回答:
MonadError
基本上是与
EitherT
对应的类型类。您可以将其实例添加到
EitherT
(出于某种原因,它在
库中被注释掉):

然后,定义您自己的方法,这些方法被推广到
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

耶,谢谢。。。我试着用电梯。举起抬起
,想知道为什么它不起作用。我想使用
EitherT
,因为
errors
包的作者似乎不愿意在这里使用
error
@Florian
errors
包的作者。我讨论了我不使用
error
的原因。但是,之所以没有
MonadError
实例,是因为我正在研究一种更具原则性的替代方法,它允许
catch
更改错误值的类型。我知道这听起来很琐碎,但我偏于谨慎,尤其是当我知道存在更好的解决方案时,因为添加功能比删除功能更容易。@GabrielGonzalez听起来很合理。。。但在此之前,这并不方便:(@Florian是的,我知道。而且,从技术上讲,这是我无法控制的,因为我一开始甚至都没有维护
包。你总是可以为自己的项目定义自己的
MonadError
实例作为最后手段。这并不难。只要复制
ErrorT的
MonadError
实例即可>从
mtl
包。或者使用
Error
并为
Error
实例添加
OtherError
构造函数。然后你可以说
strMsg=OtherError
。它不漂亮,也毫无用处,但是必须在你的代码库中包含这些东西,因为作者r of
EitherT
觉得这不是他的工作。我试着从error切换到EitherT,我刚回去就意识到我浪费了很多时间。monad transformer有什么不能正确堆叠的地方?
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