我花了一半时间试图弄清楚如何使用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))
只要我只使用ReaderT
和StateT
函数,这个效果就会很好。我的印象是,现在我应该可以这样写:
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
中徘徊。但我无法得到任何东西。
答案 0 :(得分:9)
您不能直接使用left
和hoistEither
函数的原因与mtl
包中的StateT
和ReaderT
不同,{ {3}}包不提供类似于MonadReader
或MonadState
的类型类。
前面提到的类型类可以透明地处理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
工具left
和hoistEither
的定义:
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
和hoistEither
的通用实现,这些实现适用于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