haskell - 管理状态 - SICP 第 3 章

标签 haskell state sicp state-monad

我一直在处理 Structure and Interpretation of Computer Programs并在 Haskell 中完成练习。前两章很好(代码为 github),但第 3 章让我更加努力地思考。

首先讨论管理状态,以银行账户为例。他们定义了一个函数make-withdraw经过

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

这样您就可以执行以下代码:
(define w1 (make-withdraw 100))
(define w2 (make-withdraw 100))

(w1 50)
50

(w2 70)
30

(w2 40)
"Insufficient funds"

(w1 40)
10

我不确定如何在 Haskell 中模拟这一点。我首先想到了一个使用 State monad 的简单函数:
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 .有什么建议吗?

最佳答案

Scheme 代码偷偷地使用了两个状态位:一个是变量之间的(隐式)关联 w1w2和一个引用单元;另一个是存储在引用单元中的(显式)状态。在 Haskell 中有几种不同的方法可以对此进行建模。例如,我们可能会使用 ST 提取类似的 ref-cell 技巧。 :

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。
type AccountNumber = Int
type Balance = Float
data BankState = BankState
    { nextAccountNumber :: AccountNumber
    , accountBalance :: Map AccountNumber Balance
    }

当然,我们基本上会重新实现 ref-cell 操作:
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

关于haskell - 管理状态 - SICP 第 3 章,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10048213/

相关文章:

lambda - 在方案中使用 lambda 而不是 let

string - Haskell:无法将预期类型 '[a0]' 与实际类型 '([char], [int])' 匹配

haskell - haskell出队的不变性

c - 如何打印有关我的代码执行的数据?

scroll - 调试帮助 : Flutter StatefulWidget doesn't maintain state(s) after scrolled out of view

module - 两个 Racket 模块碰撞

haskell - ghci 中的命令 ls 或 dir

.net - 在不使用 Select Case 语句的情况下,如何在表单状态更改时处理启用/禁用工具条按钮?

reactjs - 为什么 typescript 允许我覆盖空状态对象?

emacs - 在 GNU Emacs 中执行 LISP 程序