代码之家  ›  专栏  ›  技术社区  ›  Emmanuel Touzery

使用EitherT处理异常

  •  2
  • Emmanuel Touzery  · 技术社区  · 10 年前

    我已经问了一个类似的问题: exceptions and monad transformers 但不知怎么的,我没有正确表达自己,得到了另一个问题的答案,而不是我想问的问题(至少我是这样解释的)。

    我现在再次遇到这个问题,让我再次尝试阐述我的问题。。。

    我必须编写一个函数,获取一个可能保存身份验证密钥的服务器和一个保存身份验证关键内容的目标文件。

    saveAuthKey :: Text -> Server -> IO (Either Text Text)
    

    函数可能返回 Left 在三种情况下:

    1. 目标路径格式错误:不以“file://”开头
    2. 服务器不持有身份验证密钥
    3. 将密钥保存到文件时发生IO错误

    这是 EitherT 在我看来。

    所以我从以下几点开始:

    {-# LANGUAGE OverloadedStrings #-}
    
    import Control.Error
    import Control.Monad.Trans
    import Data.Text (Text)
    import qualified Data.Text as T
    import Data.ByteString (ByteString)
    import qualified Data.ByteString as BS
    import Control.Exception
    
    data Server = Server { authKey :: Maybe ByteString }
    
    main = putStrLn "OK"
    
    saveAuthKey :: Text -> Server -> IO (Either Text Text)
    saveAuthKey path server = do
        result <- try $ runEitherT $ do
            targetFile <- hoistEither $ note "Invalid target file name"
                $ T.stripPrefix "file://" path
            key <- hoistEither $ note "No authentication key for that server!"
                $ authKey server
            lift $ BS.writeFile (T.unpack targetFile) key
    

    我申请了 try runEitherT 所以 尝试 将结果包装到另一个 Either 。不太优雅。但如果我不直接进行尝试,则不会捕捉到异常。在我之前的问题中,我试图将 尝试 旁边的 lift 内部 运行EitherT ,这也不起作用。

    那么,如果你必须用这个签名写这样一个函数,你会如何处理它?我也理解我应该让 一些 异常通过而不是系统捕获 SomeException ,我认为这与我的问题没有直接关系。让我们用 尝试 我将捕获相关错误(磁盘已满、无写权限等)。

    我做不到 尝试 并让调用者处理它(在所有这些函数都在 IO monad,因此存在风险),但在某些情况下,有人必须使用 尝试 。在我的情况下,我使用 hsqml 库,这是一个在haskell中处理的Javascript调用,如果我让异常通过应用程序将崩溃。

    编辑 :我提交了当前解决此问题的方法 in this commit 然而,我觉得可以在这个功能中实现更好的功能,而不必更改应用程序其余部分的设计。请注意,我捕捉到了所有的异常,我知道这是不建议的,但现在就可以了。真的没有什么比这更好的了吗?还是我完全错误地看待这个问题?

    1 回复  |  直到 8 年前
        1
  •  1
  •   Rufflewind    10 年前

    也许这就是你想要做的?我推了 try 进入可能抛出的特定调用,并利用 bimapEitherT 将异常转换为 Text .

    saveAuthKey :: ObjRef ProjectViewState -> Text -> ObjRef (Entity Server) -> IO (Either Text Text)
    saveAuthKey _ path (entityVal . fromObjRef -> server) = runEitherT $ do
      (targetFile, key) <- hoistEither $
         (,) <$> note "Invalid target file name"
                 (T.stripPrefix "file://" path)
             <*> note "No authentication key for that server!"
                 (serverAuthKey server)
      bimapEitherT textEx (const mempty) . EitherT . try $
        BS.writeFile (T.unpack targetFile) key
    

    但是,我觉得这有点过分,因为可以抛出异常的部分被本地化为一个调用( BS.writeFile )而可以返回的部分 Left 都是事先发生的纯计算。 EitherT 当你有代码将 Either IO 逻辑很重,但这里的分离非常清楚。以下是我在没有 EitherT公司 :

    saveAuthKey :: ObjRef ProjectViewState -> Text -> ObjRef (Entity Server) -> IO (Either Text Text)
    saveAuthKey _ path (entityVal . fromObjRef -> server) =
      either (return . Left) save authKey
      where authKey = (,) <$> note "Invalid target file name"
                              (T.stripPrefix "file://" path)
                          <*> note "No authentication key for that server!"
                              (serverAuthKey server)
            save (targetFile, key) = either (Left . textEx) (const (Right ""))
                                 <$> try (BS.writeFile (T.unpack targetFile) key)