Error handling 它是怎么工作的?
我花了半天的时间试图找出如何使用EitherT来处理代码中的错误 我已经定义了这样一个转换器堆栈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
-- 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
或者,如果为文本
定义了错误
实例,则可以使用frommtl
instance Error T.Text where
strMsg = T.pack
现在您可以更改Stuff
implementleft
和的定义,如下所示:
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
@Florianerrors
包的作者。我讨论了我不使用error
的原因。但是,之所以没有MonadError
实例,是因为我正在研究一种更具原则性的替代方法,它允许catch
更改错误值的类型。我知道这听起来很琐碎,但我偏于谨慎,尤其是当我知道存在更好的解决方案时,因为添加功能比删除功能更容易。@GabrielGonzalez听起来很合理。。。但在此之前,这并不方便:(@Florian是的,我知道。而且,从技术上讲,这是我无法控制的,因为我一开始甚至都没有维护或包。你总是可以为自己的项目定义自己的MonadError
实例作为最后手段。这并不难。只要复制ErrorT的MonadError
实例即可>从mtl
包。或者使用Error
并为Error
实例添加OtherError
构造函数。然后你可以说strMsg=OtherError
。它不漂亮,也毫无用处,但是必须在你的代码库中包含这些东西,因为作者r ofEitherT
觉得这不是他的工作。我试着从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