2016-05-29 28 views
4

我有一个非常简单的抽象来处理可以回滚(在某种程度上)的IO动作序列,即如果动作写入文件,则回滚将删除此文件或动作创建一个目录树,修剪它会回滚等回滚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

像这样的monad看起来类似于[ResourceT](http://hackage.haskell.org/package/resourcet-1.1.7.3/docs/Control-Monad-Trans-Resource.html):“修改过的ReaderT monad转换器保持对仍然要执行的所有发布操作的可变引用“。区别在于清理操作只能在出现错误时执行。鉴于'ResourceT'为用户提供了对其[内部状态]的访问权限(http://hackage.haskell.org/package/resourcet-1.1.7.3/docs/Control-Monad-Trans-Resource.html#g:9)也许它可以作为“RollbackT”实现的基础。 – danidiaz

回答

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。但这可能是完全正确的。

+0

一旦成功,删除回滚操作看起来很烦人;你可以评论一下如何做到这一点吗? –

+0

我不明白;如果计算成功,则忽略回滚列表。 – ondra