2012-07-12 43 views
5

Tôi muốn soạn các thao tác có thể không thành công, nhưng có cách quay lại.Hoạt động giống nguyên tử có thể ghép nối

Ví dụ: cuộc gọi bên ngoài để đặt phòng khách sạn và cuộc gọi bên ngoài để tính phí thẻ tín dụng. Cả hai cuộc gọi đó có thể không thành công như không còn phòng, thẻ tín dụng không hợp lệ. Cả hai đều có cách quay trở lại - hủy phòng khách sạn, hủy bỏ phí tín dụng.

  1. Có tên cho loại nguyên tử này (không thực) không. Bất cứ khi nào tôi tìm kiếm giao dịch haskell, tôi nhận được STM.
  2. Có cách trừu tượng, cách soạn thảo hoặc thư viện trong haskell hoặc bất kỳ ngôn ngữ nào khác không?

Tôi cảm thấy bạn có thể viết một đơn nguyên Atomic T sẽ theo dõi các hoạt động này và cuộn lại nếu có ngoại lệ.

Edit:

Các hoạt động này có thể IO hoạt động. Nếu các hoạt động chỉ là hoạt động bộ nhớ, như hai câu trả lời gợi ý, STM sẽ đủ.

Ví dụ: đặt phòng khách sạn sẽ thông qua các yêu cầu HTTP. Các hoạt động cơ sở dữ liệu như chèn bản ghi thông qua giao tiếp socket.

Trong thế giới thực, đối với các hoạt động không thể đảo ngược, có thời gian gia hạn trước khi thực hiện thao tác - ví dụ: thanh toán bằng thẻ tín dụng và đặt phòng khách sạn có thể được thanh toán vào cuối ngày và do đó, bạn có thể hủy trước đó.

Trả lời

5

Nếu bạn cần phải nghỉ mát để làm đơn nguyên của riêng bạn, nó sẽ giống như thế này:

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) 

(Bạn có thể sẽ muốn FunctorApplicative trường hợp cũng có, nhưng chúng tầm thường.)

bạn sẽ xác định những hành động thô sơ rollbackable bạn theo cách này:

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 

Sau đó chạy chúng như vậy:

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

Đây chính xác là mục đích của STM. Các hành động được tạo thành để chúng thành công hoặc thất bại với nhau, tự động.

Rất giống với vấn đề phòng khách sạn của bạn là ví dụ giao dịch ngân hàng trong chương Simon Peyton-Jones trong "Mã Beautiful": http://research.microsoft.com/en-us/um/people/simonpj/papers/stm/beautiful.pdf

+0

Ah, nhưng STM đặc biệt nghiêm cấm IO. Câu hỏi này hỏi về các hành động IO có thể được đảo ngược nếu cần thiết với một hành động IO thứ hai. –

+1

STM là rất tương thích với các hành động IO - nó chỉ không thực hiện chúng trực tiếp (http://book.realworldhaskell.org/read/software-transactional-memory.html). Không phải tất cả IO * có thể * được giao dịch - không có hành động 'rollback' cho' launchMissiles' – amindfv

+1

Tôi không thấy câu hỏi đó đề cập cụ thể đến 'IO'. Nhưng trong mọi trường hợp, câu trả lời cho 'IO' là nó không thể được thực hiện, bởi vì đơn nguyên' IO' có các phép toán hủy diệt thông tin một cách không hủy ngang, và không có cách nào để sao chép trạng thái. Thực tế, đơn nguyên 'IO' được thiết kế đặc biệt cho điều đó! –

1

Nếu tính toán của bạn có thể được thực hiện chỉ với TVar như mọi thứ sau đó STM là hoàn hảo.

Nếu bạn cần một tác dụng phụ (như "sạc Bob $ 100") và nếu có một lỗi sau phát hành thông báo rút lại (như "hoàn Bob $ 100") sau đó bạn cần, Đánh trống xin vui lòng: 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 

Giống như Control.Exception.bracket, nhưng chỉ thực hiện hành động cuối cùng nếu có một ngoại lệ được tăng lên bởi tính toán ở giữa.

Như vậy tôi có thể tưởng tượng được sử dụng này như:

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

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

Hãy chắc chắn rằng bạn hiểu nơi để sử dụng các hoạt động Control.Exception.mask nếu bạn đang ở trong một chương trình đa luồng và thử những điều như thế này.

Và tôi nên nhấn mạnh rằng bạn có thể và nên đọc Mã nguồn để Control.ExceptionControl.Exception.Base để xem cách thực hiện điều này trong GHC.

+0

Một sử dụng Control.Exception.throwIO bên trong giao dịch để buộc nó quay trở lại. Một trong những nhu cầu để bọc (safe'charge ...) trong một cái gì đó để thực sự bắt ngoại lệ (bắt *, xử lý *, hãy thử *). –

0

Bạn thực sự có thể làm điều này với ứng dụng thông minh STM. Điều quan trọng là để tách ra các bộ phận IO. Tôi cho rằng sự cố là một giao dịch có thể xuất hiện để thành công ban đầu và không chỉ sau này. (Nếu bạn có thể nhận thất bại ngay lập tức, hoặc không lâu sau, mọi thứ đang đơn giản):

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() 

Bây giờ, không có gì ở đây mà MVars không thể xử lý: tất cả chúng ta đang làm là forking một thread để chờ xem nếu chúng ta cần sửa chữa mọi thứ. Nhưng có thể bạn sẽ làm một số nội dung khác với phí thẻ và đặt phòng khách sạn của bạn ...

+1

Có thể chạy forkIO (hoặc bất kỳ IO) nào từ STM không? – user1138184

+0

No. Nhưng phần lớn thời gian mã trên nằm trong 'IO', không phải' STM'. Khi bạn cần phải ở trong 'STM' trong một thời gian, bạn chỉ cần sử dụng' nguyên tử'. – Zopa

Các vấn đề liên quan