5

I want to compose operations that may fail, but there is a way to roll back.

For example - an external call to book a hotel room, and an external call to charge a credit card. Both of those calls may fail such as no rooms left, invalid credit card. Both have ways to roll back - cancel hotel room, cancel credit charge.

  1. Is there a name for this type of (not real) atomic. Whenever i search for haskell transaction, I get STM.
  2. Is there an abstraction, a way to compose them, or a library in haskell or any other language?

I feel you could write a monad Atomic T which will track these operations and roll them back if there is an exception.

Edit:

These operations may be IO operations. If the operations were only memory operations, as the two answers suggest, STM would suffice.

For example booking hotels would via HTTP requests. Database operations such as inserting records via socket communication.

In the real world, for irreversible operations there is a grace period before the operation will be done - e.g. credit cards payments and hotel bookings may be settled at the end of the day, and therefore it is fine to cancel before then.

4

4 回答 4

6

这正是STM的目的。操作是组合在一起的,因此它们会自动一起成功或失败。

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

于 2012-07-12T18:38:19.537 回答
5

If you need to resort to making your own monad, it will look something like this:

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)

(You will probably want Functor and Applicative instances also, but they're trivial.)

You would define your rollbackable primitive actions in this way:

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

Then run them like so:

runRollbackable
   $ do price <- rollbackableReserveRoom roomRequirements when
        paymentRef <- rollbackableChargeCreditCard ccno price
        -- etc
于 2012-07-12T22:39:00.390 回答
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

Like 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 中是如何完成的。

于 2012-07-12T19:37:14.437 回答
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 无法处理的:我们所做的就是派生一个线程来等待,看看我们是否需要修复问题。但是你可能会用你的卡费和酒店预订做一些其他的事情......

于 2012-07-12T23:17:16.417 回答