# SICP：赋值和局部状态（Python实现）

——赫拉克利特

——僧肇

• 第一种策略将注意力集中在对象（objects）上，将一个大型系统看成不同对象的集合，它们的状态和行为可能随着时间不断变化。

• 另一种组织策略将注意力集中在流过系统的信息流（streams of information）上，非常像EE工程师观察一个信号处理系统。

## 3.1.1 局部状态变量

withdraw(25) # 70
withdraw(25) # 50
withdraw(60) # "In sufficient funds"
withdraw(15) # 35


balance = 100
def withdraw(amount):
global balance
if balance > amount:
balance = balance - amount
return balance
else:
return "Insufficient funds"


def new_withdraw():
balance = 100
def inner(amount):
nonlocal balance
if balance > amount:
balance = balance - amount
return balance
else:
return "Insufficient funds"
return inner

W = new_withdraw()
print(W(25)) # 70
print(W(25)) # 50
print(W(60)) # "In sufficient funds"
print(W(15)) # 35


def make_withdraw(balance):
def withdraw(amount):
nonlocal balance
if balance > amount:
balance = balance - amount
return balance
else:
return "Insufficient funds"
return withdraw


W1 = make_withdraw(100)
W2 = make_withdraw(100)
print(W1(50)) # 50
print(W2(70)) # 30
print(W2(40)) # Insufficient funds
print(W1(40)) # 10


def make_account(balance):
def withdraw(amount):
nonlocal balance
if balance >= amount:
balance = balance - amount
return balance
else:
return "In sufficient funds"
def deposit(amount):
nonlocal balance
balance = balance + amount
return balance
def dispatch(m):
nonlocal balance
if m == "withdraw":
return withdraw
if m == "deposit":
return deposit
else:
raise ValueError("Unkown request -- MAKE_ACOUNT %s" % m)
return dispatch


acc = make_account(100)
print(acc("withdraw")(50)) # 50
print(acc("withdraw")(60)) # In sufficient funds
print(acc("deposit")(40)) # 90
print(acc("withdraw")(60)) # 30


acc的每次调用将返回局部定义的deposit或者withdraw过程，这个过程随后被应用于给定的amount。就像make_withdraw一样，对make_amount的另一次调用

acc2 = make_acount(100)


def make_accumulator(sum_value):
def accumulator(number):
nonlocal sum_value
sum_value += number
return sum_value
return accumulator

A =  make_accumulator(5)
print(A(10)) # 15
print(A(10)) # 25


(defun make_accumulator (sum_value)
(lambda (number) (incf sum_value number)))


Ruby的写法与Lisp几乎完全相同：

def make_accumulator (sum_value)
lambda {|number| sum_value += number } end


《黑客与画家》中作者还展示了Perl、Smalltalk、JavaScript等语言的写法，感兴趣的朋友可以去看下这本书（剧透一下：作者这儿把Java黑惨了233）。

## 3.1.2 引进赋值带来的利益

x2 = random_update(x1)
x3 = random_update(x2)


def rand_update(x):
a = int(pow(7, 5))
b = 0
m = int(pow(2, 31)) - 1
return (a * x + b) % m


Knuth的TAOCP第二卷(半数值算法)[3]中包含了有关随机数序列和建立起统计性质的深入讨论。注意，random_update是计算一个数学函数，两次给它同一个输入，它将产生同一个输出。这样，如果“随机”强调的事序列中每个数与前面的数无关的话，由random_update生成的数序列肯定不是“随机的”。在“真正的随机性”与所谓伪随机序列（由定义良好的确定性计算产生出的但又具有适当统计性质的序列）之间的关系是一个非常复杂的问题，涉及到数学和哲学中的一些困难问题，Kolmogorov、Solomonoff、Chaitin为这些问题做出了很多贡献，从Chaitin 1975[4]可以找到有关讨论。

def make_rand(random_init):
x = random_init
def inner():
nonlocal x
x  = rand_update(x)
return x
return inner

rand = make_rand(42)
print(rand()) # 705894
print(rand()) # 1126542223


rand = make_rand(42)
import math
def estimate_pi(trials):
return math.sqrt(6 / monte_carlo(trials, cesaro_test))

def cesaro_test():
return math.gcd(rand(), rand()) == 1

def monte_carlo(trials, experiment):
def iter(trials_remaining, trials_passed):
if trials_remaining == 0:
return trials_passed / trials
elif cesaro_test():
return iter(trials_remaining - 1, trials_passed + 1)
else:
return iter(trials_remaining - 1, trials_passed)
return iter(trials, 0)

print(estimate_pi(500)) # 3.178208630818641


random_init = 42
def estimate_pi(trials):
return math.sqrt(6 / random_gcd_test(trials, random_init))

def random_gcd_test(trials, initial_x):
def iter(trials_remaining, trials_passed, x):
x1 = rand_update(x)
x2 = rand_update(x1)
if trials_remaining == 0:
return trials_passed / trials
elif math.gcd(x1, x2) == 1:
return iter(trials_remaining - 1, trials_passed + 1, x2)
else:
return iter(trials_remaining - 1, trials_passed, x2)
return iter(trials, 0, initial_x)

print(estimate_pi(500)) # 3.178208630818641


## 3.1.3 引进赋值的代价

def make_simplified_withdraw(balance):
def simplified_withdraw(amount):
nonlocal balance
balance = balance - amount
return balance
return simplified_withdraw

W = make_simplified_withdraw(25)
print(W(20)) # 5
print(W(10)) # -5


def make_decrementer(balance):
return lambda amount: balance - amount


make_decrementer返回的是一个过程，该过程从指定的量balance中减去其输入，但顺序调用时却不会像make_simplifed_withdraw那样产生累积的结果。

D = make_decrementer(25)
print(D(20)) # 5
print(D(10)) # 15


make_decrementer(25)(20)


(lambda amount: 25 - amount) (20)


25 - 20


make_simplified_withdraw(25)(20)


(lambda amount： balance = 25 - amount)(25)(20)


(balance = 25 - 20)(25)


D1 = make_decrementer(25)
D2 = make_decrementer(25)


D1D2是同一的吗？“是”是一个可接受的回答，因为D1D2具有同样的计算行为——都是同样的将会从其输入里减去25点过程。事实上，我们确实可以在任何计算中用D1代替D2而不会改变结果，如下所示：

print(D1(20)) # 5
print(D1(20)) # 5
print(D2(20)) # 5


W1 = make_simplified_withdraw(25)
W2 = make_simplified_withdraw(25)


W1W2是同一的吗？显然不是，因为对W1W2的调用会有不同的效果，下面的调用显示出这方面的情况：

print(W1(20)) # 5
print(W1(20)) # -15
print(W2(20)) # 5


peter_acc = make_account(100)
paul_acc = make_account(100)


peter_acc = make_account(100)
paul_acc = peter_acc


peter_acc("withdraw")(10)
print(paul_acc("withdraw")(10)) # 90


peter_acc("withdraw")(10)
print(paul_acc("withdraw")(10)) # 80


def factorial(n):
def iter(product, counter):
if counter > n:
return product
else:
return iter(counter * product, counter + 1)
return iter(1, 1)

print(factorial(4)) # 24


def factorial(n):
product, counter = 1, 1
def iter():
nonlocal product, counter
if counter > n:
return product
else:
product = counter * product
counter = counter + 1
return iter()
return iter()

print(factorial(4)) # 24


counter = counter + 1
product = counter * product


print(factorial(4)) # 120， Wrong!


## 参考

• [1] Abelson H, Sussman G J. Structure and interpretation of computer programs[M]. The MIT Press, 1996.
• [2] Graham P. Hackers & painters: big ideas from the computer age[M]. " O'Reilly Media, Inc.", 2004
• [3] MacLaren M D. The art of computer programming. Volume 2: Seminumerical algorithms (Donald E. Knuth)[J]. SIAM Review, 1970, 12(2): 306-308.
• [4] Chaitin G J. Randomness and mathematical proof[J]. Scientific American, 1975, 232(5): 47-53.
• [5] Lampson B W, Horning J J, London R L, et al. Report on the programming language Euclid[J]. ACM Sigplan Notices, 1977, 12(2): 1-79.
• [6] Steele Jr G L. Debunking the “expensive procedure call” myth or, procedure call implementations considered harmful or, lambda: The ultimate goto[C]//Proceedings of the 1977 annual conference. 1977: 153-162.
posted @ 2023-03-06 22:10  orion-orion  阅读(169)  评论(0编辑  收藏  举报