我有一个非常简单的抽象来处理可以回滚(在某种程度上)的一系列IO动作,即如果一个动作写一个文件,那么回滚将删除这个文件或者一个动作创建一个目录树,修剪它将是回滚等。
data IOAction = IOAction {
execute :: IO (),
rollback :: IO ()
}
executeAll :: [IOAction] -> IO ()
executeAll [] = return ()
executeAll (a : as) = do
execute a
executeAll as `catch` rollbackAndRethrow
where
rollbackAndRethrow :: SomeException -> IO ()
rollbackAndRethrow e = rollback a >> throw e
它完全符合我的要求,但我有一种强烈的预感,即有更多可组合和更可靠(在异常处理意义上)的方法。所以我的问题是,我可以使用某个库中已知的monad变换器来实现相同的想法吗?
有类似
的东西writeFilesAtomically :: CanRollbackT IO ()
writeFilesAtomically = do
a1 <- (writeFile a str1) `orRollback` removeFile a
a2 <- (writeFile x str2) `orRollback` removeFile x
....
比当前解决方案更方便。
答案 0 :(得分:1)
这看起来非常像WriterT
monad与ExceptT
的结合。你可能会做这样的事情:
orRollback action rollaction = do
eres <- liftIO $ try action
case eres of
Right res -> do
tell [rollaction]
return res
Left (e :: SomeException) -> throwE e
然后称之为:
runMyComputation computation = do
(res, rolls) <- runWriterT $ runExceptT $ computation
case res of
Right r -> return r
Left e -> do
sequence_ (reverse rolls) -- Handle errors in rollbacks?
throwIO e
我没有测试它,但这个想法应该有效。如果您经常看到异常,则可能需要比[]
更好的monoid。
由于您无法对非IO操作执行onRollback
,因此无法完成此操作。但那可能完全没问题。