2012-07-12 29 views
5

我想編寫可能失敗的操作,但有一種方法可以回滾。可組合原子類操作

例如 - 一個預訂酒店房間的外部電話,以及一個外部電話來對信用卡收費。這些電話都可能失敗,例如沒有剩餘房間,無效的信用卡。兩種方式都可以回滾 - 取消酒店房間,取消信用額度。

  1. 是否有這種類型的(不是真正的)原子的名稱。每當我搜索haskell事務,我得到STM
  2. 是否有抽象,合成它們的方法,或haskell或任何其他語言的庫?

我覺得你可以編寫一個monad Atomic T,它將跟蹤這些操作並在出現異常時將其回滾。

編輯:

這些操作可能是IO操作。如果操作只是記憶操作,正如這兩個答案所暗示的,STM就足夠了。

例如預訂酒店將通過HTTP請求。數據庫操作,例如通過套接字通信插入記錄。

在現實世界中,對於不可逆轉的操作,在操作完成之前有一段寬限期 - 例如,信用卡付款和酒店預訂可能在一天結束時結算,因此在此之前取消可以。

回答

5

如果你需要求助於使自己的單子,它會是這個樣子:

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情況也有,但是他們是微不足道的。)

你會以這種方式定義rollbackable基本動作:

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 
6

這正是STM的目的。操作的組成是自動成功或失敗。

非常相似,你的酒店房間,問題是西蒙·佩頓 - 瓊斯在「美麗密碼」一章中的銀行交易的例子:如果你的計算可以做的只有TVar喜歡的東西,然後STM是完美http://research.microsoft.com/en-us/um/people/simonpj/papers/stm/beautiful.pdf

+0

啊,但STM明確禁止IO。這個問題詢問IO操作,如果有必要,可以使用第二個IO操作來反轉。 – 2012-07-12 19:03:04

+1

STM與IO操作非常兼容 - 它只是不直接執行它們(http://book.realworldhaskell.org/read/software-transactional-memory.html)。不是所有的IO *都可以是事務性的 - 對'launchMissiles'沒有'rollback'動作 – amindfv 2012-07-12 19:38:16

+1

我沒有看到這個問題明確提到'IO'。但在任何情況下,IO的答案都是無法完成的,因爲IO IO monad具有不可撤消地破壞信息的操作,無法複製狀態。實際上,'IO' monad是專門爲此設計的! – 2012-07-12 19:40:38

1

如果你需要的副作用(如「充電鮑勃$ 100」),如果有一個錯誤後發出收縮(如「退款鮑勃$ 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中完成的。

+0

在事務內部使用Control.Exception.throwIO強制它回滾。一個人需要將某個東西包裹(safe'charge ...)以實際捕獲異常(catch *,handle *,try *)。 – 2012-07-12 19:45:38

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無法處理:所有我們正在做的是派生一個線程等待和觀望如果我們需要修理東西。但你大概會做一些其他的東西與您的卡收費和酒店預訂...

+1

是否可以從STM運行forkIO(或任何IO)? – user1138184 2012-07-12 23:56:47

+0

否。但大部分時間上面的代碼是在'IO'中,而不是'STM'。當你需要使用'STM'一段時間時,只需使用'atomically'。 – Zopa 2012-07-13 00:02:01