管理国家 - SICP第3章 [英] Managing state - chapter 3 of SICP
问题描述
我一直在计算机程序的结构和解释中完成并完成Haskell中的练习。前两章没有问题(代码见 github ),但第3章让我觉得很难。
首先讨论管理状态,以银行账户为例。他们通过
定义了一个函数 make-withdraw
(平衡金额)
(lambda(金额)
(如果(> =余额金额)
(开始(设置!余额( - 余额金额))
余额)
资金不足)))
以便您可以执行以下代码:
(define w1(make-withdraw 100))
(define w2(make-withdraw 100))
(w1 50)
50
(w2 70)
30
(w2 40)
资金不足
(w1 40)
10
我不确定我可以在Haskell中模拟这个。我首先想到了使用State monad的一些简单函数:
$ p $ import $ Control $ $
类型Cash = Float
类型Account =状态Cash
withdraw ::现金 - >账户(任一字符串现金)
提取金额=国家提款其中
提取余额=如果余额> =金额
那么(正确金额,余额 - 金额)
其他(左资金不足,余额)
允许我运行代码
ghci> runState(do 50; withdraw 40})100
(剩下资金不足,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]
[右50,右70,左资金不足,右40]
但我不确定如何编写函数 makeWithdraw
。任何建议吗?
Scheme代码使用两个状态位偷偷摸摸地进行:一个是变量<隐式c $ c> w1 和 w2
和一个ref-cell;另一个是存储在ref-cell中的(显式)状态。在Haskell中有几种不同的方法来模拟它。例如,我们可能会用 ST
来提取类似的引用细胞技巧:
makeWithdraw :: Float - > ST s(Float - > ST s(或者String Float))
makeWithdraw initialBalance = do
refBalance< - newSTRef initialBalance
return $ \amount - >做
余额< - readSTRef refBalance
让余额'=余额 - 金额
如果余额'< 0
然后返回(左不足资金)
else writeSTRef refBalance余额'>>
这可以让我们做到这一点:
* 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 |返回[x1,y1,y2,x2]
* Main | :}
[右50.0,右30.0,左资金不足,右10.0]
另一个选项是使两个状态都是显式的,例如通过将每个帐户与唯一的 Int
id关联。
类型AccountNumber = Int
类型Balance =浮动
数据BankState = BankState
{nextAccountNumber :: AccountNumber
,accountBalance :: Map AccountNumber余额
}
当然,我们基本上会重新执行ref-单元格操作:
newAccount :: Balance - > State BankState AccountNumber
newAccount balance = do
next< - gets nextAccountNumber
modify $ \bs - > bs
{nextAccountNumber = next + 1
,accountBalance =插入下一个余额(accountBalance bs)
}
返回下一个
withdraw :: Account - > ;余额 - > State BankState(任一字符串余额)
提取帐户金额= do
余额< - 从(可能为0。lookup account。accountBalance)
let balance'= balance - amount
if平衡' 0
然后返回(左不足资金)
else修改(\ bs - > bs {accountBalance =插入帐户余额'(accountBalance bs)})>> return(Right balance')
然后让我们写 makeWithdraw
:
makeWithDraw :: Balance - > State BankState(余额 - >国家BankState(任一字符串余额))
makeWithdraw余额=提取< $> newAccount余额
I've been working through in Structure and Interpretation of Computer Programs and completing the exercises in Haskell. The first two chapters were fine (code at github) but Chapter 3 is making me think harder.
It starts by talking about managing state, with the example of a bank account. They define a function make-withdraw
by
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")))
so that you can execute the following code:
(define w1 (make-withdraw 100))
(define w2 (make-withdraw 100))
(w1 50)
50
(w2 70)
30
(w2 40)
"Insufficient funds"
(w1 40)
10
I'm not sure how I can emulate this in Haskell. I first thought to a some simple function using the 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)
which allows me to run the code
ghci> runState (do { withdraw 50; withdraw 40 }) 100
(Left "Insufficient funds",30.0)
but that does something different to the scheme code. Ideally I'd be able to run something like
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]
but I'm not sure how to write the function makeWithdraw
. Any advice?
The Scheme code is sneakily using two bits of state: one is the (implicit) association between variables w1
and w2
and a ref-cell; the other is the (explicit) state stored in a ref-cell. There's a couple different ways to model this in Haskell. For example, we might pull a similar ref-cell trick with 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')
Which lets us do this:
*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]
Another option is to make both pieces of the state explicit, for example by associating each account with a unique Int
id.
type AccountNumber = Int
type Balance = Float
data BankState = BankState
{ nextAccountNumber :: AccountNumber
, accountBalance :: Map AccountNumber Balance
}
Of course, we would then basically be re-implementing the ref-cell operations:
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')
Which would then let us write makeWithdraw
:
makeWithDraw :: Balance -> State BankState (Balance -> State BankState (Either String Balance))
makeWithdraw balance = withdraw <$> newAccount balance
这篇关于管理国家 - SICP第3章的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!