2012-07-12 4 views
5

실패 할 수있는 작업을 작성하려고하지만 롤백 할 수있는 방법이 있습니다.조합 가능한 원자 적 작업

예 : 호텔 방 예약을위한 외부 통화 및 신용 카드 청구를위한 외부 통화. 두 통화 모두 방이 남지 않았거나 신용 카드가 유효하지 않은 경우와 같이 실패 할 수 있습니다. 두 가지 방법 모두 롤백 할 수 있습니다 - 호텔 객실 취소, 신용 카드 취소.

  1. 원자 (진짜가 아닌) 원자의 이름이 있습니까? 내가 haskell 거래를 검색 할 때마다 나는 STM이된다.
  2. haskell 또는 다른 언어로 된 추상화, 구성 방법 또는 라이브러리가 있습니까?

이 작업을 추적하고 예외가있는 경우 롤백하는 모나드 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 작업에 대해 묻습니다. –

+1

STM은 IO 작업과 매우 호환되며 직접 수행하지 않습니다 (http://book.realworldhaskell.org/read/software-transactional-memory.html). 모든 IO *가 트랜잭션이 될 수있는 것은 아닙니다 - launchMissiles에 대한 '롤백'작업이 없습니다. – amindfv

+1

'IO'에 특별히 언급 된 질문이 없습니다. 그러나 어떤 경우에있어서, '입출력'모나드는 정보를 돌이킬 수 없게 파괴하는 조작을하고, 그 상태를 복제 할 방법이 없기 때문에 '입출력'에 대한 대답은 할 수 없다는 것입니다. 사실, 'IO' 모나드는이를 위해 특별히 설계되었습니다! –

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 작업을 사용하고이 같은 것들을 시도하는 위치를 알고 있어야합니다.

그리고 GHC에서 어떻게 수행되는지 보려면 소스 코드를 Control.ExceptionControl.Exception.Base으로 읽고 읽을 수 있어야합니다.당신이 당신의 자신의 모나드를 만들기에 의존해야하는 경우

+0

하나는 트랜잭션 내에서 Control.Exception.throwIO를 사용하여 강제로 롤백합니다. 실제로 예외 (catch *, handle *, try *)를 catch하려면 무언가를 랩 (safe'charge ...)해야합니다. –

0

정말 STM을 영리하게 적용하면됩니다. 열쇠는 입출력 부품을 분리하는 것입니다. 문제는 트랜잭션이 처음에는 성공한 것처럼 보일 수 있으며 나중에 만 실패합니다. (당신은 바로 실패를 인식 할 수있는 경우, 또는 곧, 상황이 간단) :

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

+0

아니요. 그러나 대부분 위의 코드는 'STM'이 아니라 'IO'에 있습니다. 잠시 동안'STM'에 있어야 할 때'atomically' 만 사용하면됩니다. – Zopa