2012-04-06 6 views
11

나는 Structure and Interpretation of Computer Programs에서 일하고 있으며 하스켈에서 연습을 마쳤습니다. 첫 번째 두 장은 괜찮 았지만 (코드는 github) 3 장은 나를 더 열심히 생각하게 만듭니다.상태 관리 - SICP의 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을 정의합니다. 나는 처음에 국가 모나드를 사용하여 몇 가지 간단한 기능을 생각 :

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) 

나 코드

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

을 실행할 수 있지만 그 계획 코드에 다른 일을한다. 이상적으로는

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

Scheme 코드는 2 비트의 상태를 사용하여 잠깐 동안 있습니다 : 하나는 변수 w1w2 사이의 (암시 적) 연관이며 ref-cell입니다. 다른 하나는 ref 셀에 저장된 (명시적인) 상태이다. 하스켈에서 이것을 모델링하는 몇 가지 다른 방법이 있습니다. 예를 들어, 우리는 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 ID를 연결합니다. 의 설명을

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

감사합니다. 훌륭한 답변입니다. –

4

글쎄, 여기에는 시스템의 각 "계정"에 대해 하나의 가변적 인 상태가 여러 개 있습니다. State 모나드는 하나만 개입니다. 당신은 같은 것을 저장할 수 있습니다. 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에 배치하면 모 다르 컨텍스트 외부에서 불순하고 변경 가능한 상태가됩니다! 따라서 각 STSTRefs 유형 매개 변수를 전달하며 runST 유형은 runST :: (forall s. ST s a) -> a입니다. 그러면 s에 대한 특정 값을 선택하지 않게됩니다. 가능한 코드는 모두 s입니다. 특정 유형을 지정하지 않습니다. 상태 스레드를 고립 상태로 유지하는 트릭으로 사용됩니다.

+0

감사합니다 : 다음 우리가 makeWithdraw을 쓸 수 것

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

: 물론

type AccountNumber = Int type Balance = Float data BankState = BankState { nextAccountNumber :: AccountNumber , accountBalance :: Map AccountNumber Balance } 

, 우리는 기본적으로 심판 셀 작업을 다시 구현 될 것이다 ST가 정말 도움이됩니다! –