2012-04-06 21 views
11

का अध्याय 3 मैं Structure and Interpretation of Computer Programs में काम कर रहा हूं और हास्केल में अभ्यास पूरा कर रहा हूं। पहले दो अध्याय ठीक थे (github पर कोड) लेकिन अध्याय 3 मुझे कठिन सोच रहा है।प्रबंधन राज्य - एसआईसीपी

यह बैंक खाते के उदाहरण के साथ राज्य के प्रबंधन के बारे में बात करके शुरू होता है।

(define w1 (make-withdraw 100)) 
(define w2 (make-withdraw 100)) 

(w1 50) 
50 

(w2 70) 
30 

(w2 40) 
"Insufficient funds" 

(w1 40) 
10 

मुझे यकीन है कि कैसे मैं हास्केल में इस का अनुकरण कर सकते हैं नहीं कर रहा हूँ: इतना है कि आप निम्नलिखित कोड निष्पादित कर सकते हैं वे द्वारा

(define (make-withdraw balance) 
    (lambda (amount) 
     (if (>= balance amount) 
      (begin (set! balance (- balance amount)) 
       balance) 
      "Insufficient funds"))) 

एक समारोह make-withdraw परिभाषित करते हैं। मैं पहली बार एक के लिए कुछ सरल समारोह राज्य इकाई का उपयोग कर सोचा: जो मुझे कोड

ghci> runState (do { withdraw 50; withdraw 40 }) 100 
(Left "Insufficient funds",30.0) 

चलाने की अनुमति देता

import Control.Monad.State 

type Cash = Float 
type Account = State Cash 

withdraw :: Cash -> Account (Either String Cash) 
withdraw amount = state makewithdrawal where 
    makewithdrawal balance = if balance >= amount 
     then (Right amount, balance - amount) 
     else (Left "Insufficient funds", balance) 

लेकिन है कि कुछ योजना कोड के लिए अलग करता है। आदर्श रूप में मैं

do 
    w1 <- makeWithdraw 100 
    w2 <- makeWithdraw 100 
    x1 <- w1 50 
    y1 <- w2 70 
    y2 <- w2 40 
    x2 <- w1 40 
    return [x1,y1,y2,x2] 

[Right 50,Right 70,Left "Insufficient funds",Right 40] 

की तरह कुछ चलाने के लिए सक्षम हो जाएगा, लेकिन मुझे यकीन है कि कैसे समारोह makeWithdraw लिखने के लिए नहीं कर रहा हूँ। कोई सलाह?

उत्तर

8

योजना कोड चुपके से राज्य के दो बिट्स का उपयोग कर रहा है: एक चर के बीच (अंतर्निहित) एसोसिएशन w1 और w2 और एक रेफ-सेल; दूसरा एक रेफ-सेल में संग्रहीत (स्पष्ट) राज्य है। हास्केल में इसे मॉडल करने के कुछ अलग तरीके हैं। उदाहरण के लिए, हम ST के साथ एक समान रेफरी सेल चाल खींच सकता है:

makeWithdraw :: Float -> ST s (Float -> ST s (Either String Float)) 
makeWithdraw initialBalance = do 
    refBalance <- newSTRef initialBalance 
    return $ \amount -> do 
     balance <- readSTRef refBalance 
     let balance' = balance - amount 
     if balance' < 0 
      then return (Left "insufficient funds") 
      else writeSTRef refBalance balance' >> return (Right balance') 

हमें ऐसा करने की सुविधा देता है कौन सा:

*Main> :{ 
*Main| runST $ do 
*Main| w1 <- makeWithdraw 100 
*Main| w2 <- makeWithdraw 100 
*Main| x1 <- w1 50 
*Main| y1 <- w2 70 
*Main| y2 <- w2 40 
*Main| x2 <- w1 40 
*Main| return [x1,y1,y2,x2] 
*Main| :} 
[Right 50.0,Right 30.0,Left "insufficient funds",Right 10.0] 

एक अन्य विकल्प द्वारा उदाहरण के लिए, राज्य के दोनों टुकड़े स्पष्ट बनाने के लिए है प्रत्येक खाते को अद्वितीय Int आईडी से जोड़ना।

newAccount :: Balance -> State BankState AccountNumber 
newAccount balance = do 
    next <- gets nextAccountNumber 
    modify $ \bs -> bs 
     { nextAccountNumber = next + 1 
     , accountBalance = insert next balance (accountBalance bs) 
     } 
    return next 

withdraw :: Account -> Balance -> State BankState (Either String Balance) 
withdraw account amount = do 
    balance <- gets (fromMaybe 0 . lookup account . accountBalance) 
    let balance' = balance - amount 
    if balance' < 0 
     then return (Left "insufficient funds") 
     else modify (\bs -> bs { accountBalance = insert account balance' (accountBalance bs) }) >> return (Right balance') 

कौन सा फिर हमें makeWithdraw लिख दिया जाएगा:

makeWithDraw :: Balance -> State BankState (Balance -> State BankState (Either String Balance)) 
makeWithdraw balance = withdraw <$> newAccount balance 
+0

धन्यवाद, यह एक अच्छा जवाब है। –

4

ठीक है, आपके पास स्वतंत्र, परिवर्तनीय स्थिति के कई टुकड़े हैं: सिस्टम में प्रत्येक "खाता" के लिए। State मोनैड केवल आपको एक राज्य का टुकड़ा देता है। आप राज्य में (Int, Map Int Cash) जैसे कुछ स्टोर कर सकते हैं, प्रत्येक बार मानचित्र में ताजा कुंजी प्राप्त करने के लिए Int को बढ़ाकर, और संतुलन को संग्रहीत करने के लिए इसका उपयोग करें ... लेकिन यह इतना बदसूरत है, है ना?

शुक्र है, हालांकि, हास्केल के स्वतंत्र, परिवर्तनीय राज्य के कई टुकड़ों के लिए एक मोनड है: ST

type Account = ST 

makeWithdraw :: Cash -> Account s (Cash -> Account s (Either String Cash)) 
makeWithdraw amount = do 
    cash <- newSTRef amount 
    return withdraw 
    where 
    withdraw balance 
     | balance >= amount = do 
      modifySTRef cash (subtract amount) 
      return $ Right amount 
     | otherwise = return $ Left "Insufficient funds" 

इसके साथ, आपके कोड उदाहरण को ठीक काम करना चाहिए; बस runST लागू करें और आपको वह सूची मिलनी चाहिए जो आप चाहते हैं। ST मोनैड बहुत आसान है: आप केवल STRef एस बना सकते हैं और संशोधित कर सकते हैं, जो नियमित रूप से परिवर्तनीय चर की तरह कार्य करता है; वास्तव में, उनका इंटरफ़ेस मूल रूप से IORef एस के समान है।

एकमात्र मुश्किल बिट अतिरिक्त s प्रकार पैरामीटर है, जिसे राज्य धागा के रूप में जाना जाता है। यह ST संदर्भ यह में बनने वाले के साथ प्रत्येक STRef संबद्ध करने के लिए प्रयोग किया जाता है यह बहुत बुरा हो सकता है अगर आप एक ST कार्रवाई से एक STRef लौट सकता है, और के लिए एक औरST संदर्भ के पार ले जाने के लिए -। ST के पूरे मुद्दे है कि आप है इसे IO के बाहर शुद्ध कोड के रूप में चलाया जा सकता है, लेकिन यदि STRef एस बच सकता है, तो आप अपने सभी परिचालनों को runST में लपेटकर, अपर्याप्त संदर्भ के बाहर अशुद्ध, उत्परिवर्तनीय स्थिति प्राप्त करेंगे! इसलिए, प्रत्येक ST और STRef उसी s प्रकार पैरामीटर के आसपास रहता है, और runST प्रकार runST :: (forall s. ST s a) -> a है। यह आपको s के लिए कोई विशेष मूल्य चुनने से रोकता है: आपके कोड को s के सभी संभावित मानों के साथ काम करना है। यह कभी भी किसी विशेष प्रकार को असाइन नहीं किया जाता है; राज्य धागे को अलग रखने के लिए बस एक चाल के रूप में प्रयोग किया जाता है।

+0

धन्यवाद, की व्याख्या

type AccountNumber = Int type Balance = Float data BankState = BankState { nextAccountNumber :: AccountNumber , accountBalance :: Map AccountNumber Balance } 
बेशक

, हम तो मूल रूप से किया जा रेफरी सेल संचालन फिर से लागू करने हैं एसटी वास्तव में सहायक है! –

संबंधित मुद्दे