2012-07-12 16 views
5

मैं उन परिचालनों को लिखना चाहता हूं जो असफल हो सकते हैं, लेकिन वापस रोल करने का एक तरीका है।कंपोजेबल परमाणु-जैसे ऑपरेशंस

उदाहरण के लिए - एक होटल के कमरे को बुक करने के लिए एक बाहरी कॉल, और क्रेडिट कार्ड चार्ज करने के लिए एक बाहरी कॉल। उन दोनों कॉल विफल हो सकती हैं जैसे कोई कमरा नहीं छोड़ा गया, अमान्य क्रेडिट कार्ड। दोनों के पास वापस रोल करने के तरीके हैं - होटल के कमरे को रद्द करें, क्रेडिट शुल्क रद्द करें।

  1. क्या इस प्रकार के (वास्तविक नहीं) परमाणु के लिए कोई नाम है। जब भी मैं हैकेल लेनदेन की खोज करता हूं, मुझे STM मिलता है।
  2. क्या कोई अमूर्तता है, उन्हें लिखने का एक तरीका है, या हैकेल या किसी अन्य भाषा में लाइब्रेरी है?

मुझे लगता है कि आप एक मोनड Atomic T लिख सकते हैं जो इन परिचालनों को ट्रैक करेगा और अपवाद होने पर उन्हें वापस रोल करेगा।

संपादित करें:

ये अभियान IO संचालन हो सकता है। यदि ऑपरेशन केवल स्मृति संचालन थे, क्योंकि दोनों उत्तरों सुझाव देते हैं, एसटीएम पर्याप्त होगा।

उदाहरण के लिए बुकिंग होटल 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) 

(आप शायद Functor और Applicative उदाहरणों भी चाहते हैं, लेकिन वे तुच्छ कर रहे हैं।)

आप इस तरीके से अपने 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

यह वास्तव में एसटीएम का उद्देश्य है। क्रियाएं रचना की जाती हैं ताकि वे स्वचालित रूप से सफल हो जाएं या विफल हो जाएं। http://research.microsoft.com/en-us/um/people/simonpj/papers/stm/beautiful.pdf

+0

आह, लेकिन एसटीएम विशेष रूप से आईओ को प्रतिबंधित करता है। यह प्रश्न आईओ कार्यों के बारे में पूछता है जिसे दूसरी आईओ कार्रवाई के साथ आवश्यक होने पर उलट किया जा सकता है। –

+1

एसटीएम आईओ कार्यों के साथ बहुत संगत है - यह सिर्फ उन्हें सीधे नहीं करता है (http://book.realworldhaskell.org/read/software-transactional-memory.html)। सभी आईओ * लेनदेन नहीं हो सकते हैं - 'लॉन्च मिस्त्री' – amindfv

+1

के लिए कोई 'रोलबैक' कार्रवाई नहीं है, मुझे नहीं लगता कि प्रश्न विशेष रूप से 'आईओ' का उल्लेख करता है। लेकिन किसी भी मामले में, 'आईओ' का उत्तर यह है कि यह नहीं किया जा सकता है, क्योंकि 'आईओ' मोनैड में ऐसे ऑपरेशन हैं जो सूचना को अपरिवर्तनीय रूप से नष्ट कर देते हैं, और राज्य को डुप्लिकेट करने का कोई तरीका नहीं है। वास्तव में, 'आईओ' मोनैड विशेष रूप से इसके लिए डिज़ाइन किया गया है! –

1

अपने संगणना किया जा सकता है, तो चीजों की तरह ही साथ TVar तो STM एकदम सही है:

बहुत अपने होटल के कमरे समस्या के समान "सुंदर कोड" में साइमन पेयटन-जोन्स के अध्याय में बैंक लेन-देन उदाहरण है।

आप एक पक्ष प्रभाव की जरूरत है (जैसे "बॉब $ 100 चार्ज") और अगर वहाँ एक त्रुटि बाद में एक तर्क ("वापसी बॉब $ 100" की तरह) जारी तो आप की जरूरत है, drumroll कृपया: 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.Exception और Control.Exception.Base पर स्रोत कोड को पढ़ना चाहिए और यह देखने के लिए कि यह जीएचसी में कैसे किया जाता है।

+0

लेन-देन के अंदर नियंत्रण के लिए नियंत्रण का उपयोग करता है। इसे वापस रोल करने के लिए मजबूर करने के लिए। किसी को वास्तव में अपवाद (पकड़ *, हैंडल *, कोशिश *) पकड़ने के लिए कुछ में लपेटने की आवश्यकता है (सुरक्षित ...)। –

0

आप वास्तव में एसटीएम के चालाक आवेदन के साथ ऐसा कर सकते हैं। आईओ भागों को अलग करना कुंजी है। मुझे लगता है कि समस्या यह है कि एक लेनदेन शुरू में सफल होने के लिए प्रकट हो सकता है, और बाद में विफल हो सकता है। (आप विफलता को पहचान तो कर सकते हैं तुरंत या ठीक बाद, चीजों को सरल कर रहे हैं):

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 को संभाल नहीं सकता है: सब हम कर रहे हैं प्रतीक्षा करें और देखने के लिए एक धागा forking है अगर हमें चीजों को ठीक करने की ज़रूरत है। लेकिन आप संभवतः अपने कार्ड शुल्क और होटल आरक्षण के साथ कुछ और सामान कर रहे होंगे ...

+1

क्या एसटीएम से फोर्कियो (या कोई आईओ) चलाने के लिए संभव है? – user1138184

+0

नहीं, लेकिन उपर्युक्त कोड उपरोक्त कोड 'आईओ' में है, न कि 'एसटीएम'। जब आपको थोड़ी देर के लिए 'एसटीएम' में होना चाहिए, तो आप केवल 'परमाणु' का उपयोग करें। – Zopa