EitherT如何运作?

fho*_*fho 8 error-handling haskell monad-transformers 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))
Run Code Online (Sandbox Code Playgroud)

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

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when s == "left" $ left "breaking out"
  "right"
Run Code Online (Sandbox Code Playgroud)

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

faultyLookup :: Map -> String -> Stuff String
faultyLookup m k = do
  hoistEither $ lookup k m
Run Code Online (Sandbox Code Playgroud)

我阅读了关于monad变形金刚的现实世界haskell章节,并在其中摆弄lift.但我无法得到任何东西.

sha*_*ang 9

你不能只是使用的原因lefthoistEither功能是直接,不像StateTReaderT来自mtl包,either包不提供类似类型类MonadReaderMonadState.

前面提到的类型类可以透明地处理monad堆栈中的提升,但是EitherT,你必须自己完成提升(或编写MonadEither类似于MonadReaderet al 的类型类).

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

首先,你需要应用Stuff的包装,然后lift通过ReaderT变压器,然后lift通过再次StateT变压器.

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

stuffLeft :: T.Text -> Stuff a
stuffLeft = Stuff . lift . lift . left
Run Code Online (Sandbox Code Playgroud)

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

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

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

instance Error T.Text where
  strMsg = T.pack
Run Code Online (Sandbox Code Playgroud)

现在你可以改变的定义,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
Run Code Online (Sandbox Code Playgroud)

使用此原始faultyFunction类型检查,无需任何手动提升.

您还可以为(使用from )的任何实例编写通用实现left并使用hoistEither它们:MonadErroreitherData.Either

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

hoistEither :: MonadError e m => Either e a -> m a
hoistEither = either throwError return
Run Code Online (Sandbox Code Playgroud)

  • @Florian这里的`errors`包的作者.我讨论了我不使用`ErrorT` [here](http://www.haskellforall.com/2012/07/errors-10-simplified-error-handling.html)的原因.但是,没有`MonadError`实例的原因是因为我正在研究一个更有原则的替代方案,它允许`catch`来改变错误值的类型.我知道这听起来微不足道,但我谨慎行事,尤其是当我知道存在更好的解决方案时,因为添加功能比删除功能更容易. (2认同)