我已经在那里问了一个类似的问题:exceptions and monad transformers但不知何故没有正确地表达自己并得到了另一个问题的答案,而不是我要问的问题(至少我是如何解释的)它)。
我现在再次遇到这个问题,让我再试一次,制定我的问题......
我必须编写一个函数来获取可能包含身份验证密钥的服务器,以及一个目标文件,用于保存身份验证密钥内容。
saveAuthKey :: Text -> Server -> IO (Either Text Text)
在三种情况下,该函数可能会返回Left
:
这似乎是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
,因此try
将结果包装在另一个Either
中。不太优雅。但如果我不直接尝试,那么这个例外就不会被抓住。在我之前的问题中,我尝试将try
放在lift
旁边的runEitherT
内,这也不能很好地运作。
所以,如果你必须用这个签名编写这样一个函数,你将如何处理它?我也理解我应该通过一些例外而不是系统地捕捉SomeException
的部分,我不认为它与我的问题直接相关。让我们说try
我会发现相关错误(磁盘已满,没有写入权限等)。
我根本无法做try
,让调用者处理它(所有这个函数都在IO
monad中,因此存在风险),但在某些时候会有人会必须使用try
。同样在我的情况下,我使用hsqml
库,这是来自haskell中处理的Javascript的调用,如果我让通过应用程序的异常会崩溃。
编辑:我将当前的解决方案提交到此问题in this commit。但是我觉得在这个功能中可以更好地实现更好的功能,而无需改变应用程序其余部分的设计。请注意,我会捕获所有异常情况,我知道这些异常情况并不存在,但它现在就会这样做。真的没有什么可以实现的吗?或者我完全以错误的方式解决问题?
答案 0 :(得分:1)
也许这就是你的意思?我将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)