可组合的原子类操作

时间:2012-07-12 17:53:16

标签: haskell transactions atomic

我想编写可能失败的操作,但有一种方法可以回滚。

例如 - 预订酒店房间的外部电话,以及向信用卡收费的外部电话。这两个电话都可能失败,例如没有房间,信用卡无效。两者都有办法回滚 - 取消酒店房间,取消信用费。

  1. 这种(非真实的)原子是否有名称?每当我搜索haskell交易时,我都会得到STM
  2. 是否有抽象,组合它们的方式,或者haskell或任何其他语言的库?
  3. 我觉得你可以编写一个monad Atomic T来跟踪这些操作并在有异常的情况下将它们回滚。

    编辑:

    这些操作可能是IO次操作。如果操作只是内存操作,正如两个答案所示,STM就足够了。

    例如通过HTTP请求预订酒店。数据库操作,例如通过套接字通信插入记录。

    在现实世界中,对于不可逆操作,在操作完成之前会有一段宽限期 - 例如信用卡付款和酒店预订可能会在当天结束时结算,因此可以在此之前取消。

4 个答案:

答案 0 :(得分:6)

这正是STM的目的。组成动作以便它们自动地一起成功或失败。

非常类似于您的酒店房间问题,是Simon Peyton-Jones在“美丽代码”章节中的银行交易示例:http://research.microsoft.com/en-us/um/people/simonpj/papers/stm/beautiful.pdf

答案 1 :(得分:5)

如果您需要使用自己的monad,它将看起来像这样:

import Control.Exception (onException, throwIO)

newtype Rollbackable a = Rollbackable (IO (IO (), a))

runRollbackable :: Rollbackable a -> IO a
runRollbackable (Rollbackable m) = fmap snd m
    -- you might want this to catch exceptions and return IO (Either SomeException a) instead

instance Monad Rollbackable where
    return x = Rollbackable $ return (return (), x)
    Rollbackable m >>= f
       = do (rollback, x) <- m
            Rollbackable (f x `onException` rollback)

(您可能也需要FunctorApplicative个实例,但它们很简单。)

您将以这种方式定义可回滚的原语操作:

rollbackableChargeCreditCard :: CardNumber -> CurrencyAmount -> Rollbackable CCTransactionRef
rollbackableChargeCreditCard ccno amount = Rollbackable
   $ do ref <- ioChargeCreditCard ccno amount
        return (ioUnchargeCreditCard ref, ref)

ioChargeCreditCard :: CardNumber -> CurrencyAmount -> IO CCTransactionRef
-- use throwIO on failure
ioUnchargeCreditCard :: CCTransactionRef -> IO ()
-- these both just do ordinary i/o

然后像这样运行它们:

runRollbackable
   $ do price <- rollbackableReserveRoom roomRequirements when
        paymentRef <- rollbackableChargeCreditCard ccno price
        -- etc

答案 2 :(得分:1)

如果你的计算只能用TVar类似的东西完成,那么STM就是完美的。

如果您需要副作用(例如“为Bob充电100美元”)并且如果稍后发出错误(例如“退款Bob $ 100”)那么您需要,请鼓励:Control.Exceptions.bracketOnError

bracketOnError
        :: IO a         -- ^ computation to run first (\"acquire resource\")
        -> (a -> IO b)  -- ^ computation to run last (\"release resource\")
        -> (a -> IO c)  -- ^ computation to run in-between
        -> IO c         -- returns the value from the in-between computation
  

Control.Exception.bracket类似,但只有在执行时才执行最终操作   中间计算引发的异常。

因此,我可以想象使用它:

let safe'charge'Bob = bracketOnError (charge'Bob) (\a -> refund'Bob)

safe'charge'Bob $ \a -> do
   rest'of'transaction
   which'may'throw'error

如果您使用的是多线程程序,请确保您了解Control.Exception.mask操作的使用位置并尝试此类操作。

我应该强调,您可以而且应该阅读源代码Control.ExceptionControl.Exception.Base,看看这是如何在GHC中完成的。

答案 3 :(得分:0)

你真的可以通过聪明的STM应用来做到这一点。关键是要分离IO部分。我假设问题是事务最初可能会成功,并且稍后才会失败。 (如果您可以立即识别失败,或者很快就会发现事情变得更简单):

main = do
   r   <- reserveHotel
   c   <- chargeCreditCard

   let room         = newTVar r
       card         = newTVar c
       transFailure = newEmptyTMVar

   rollback <- forkIO $ do 
       a <- atomically $ takeTMVar transFailure --blocks until we put something here
       case a of
         Left  "No Room"       -> allFullRollback
         Right "Card declined" -> badCardRollback

  failure <- listenForFailure -- A hypothetical IO action that blocks, waiting for
                              -- a failure message or an "all clear"
  case failures of
      "No Room"       -> atomically $ putTMVar (Left "No Room")
      "Card Declined" -> atomically $ putTMVar (Right "Card declined")
      _               -> return ()

现在,MVars无法处理这里:我们所做的只是让线程等待,看看我们是否需要解决问题。但是你可能会在你的卡收费和酒店预订方面做其他一些事情......