SICP讀書筆記 3.1
SICP CONCLUSION
讓我們舉起杯,祝福那些將他們的思想鑲嵌在重重括號之間的Lisp程序員 !
祝我能夠突破層層代碼,找到住在裏計算機的神靈!
目錄
1. 構造過程抽象
2. 構造數據抽象
3. 模塊化、對象和狀態
4. 元語言抽象
5. 寄存器機器裏的計算
Chapter 3
- 模塊化、對象和狀態
練習答案
從前兩章中我們認識到,在克服系統復雜性問題時,構造過程抽象和數據抽象祈禱了非常關鍵的作用。但是在進行組織模塊化後,我們還需要一些組織原則來完成系統的整體設計,使這些系統自然地劃分為內聚的部分
有一種強有力的設計策略,就是基於外界事物的結構,去模擬設計,也就是劃分每個計算對象,也就是面向對象思想,這一策略的好處也就是在擴展修改增加程序時,只需要做一些局部的工作
另外一種策略是將註意力集中在流過系統的信息流上
賦值和局部狀態
作為一個對象,我們就可以算它的狀態是受時間或者歷史影響的,那麽就需要表示它的狀態變量,而所謂的交互就是建立對象與對象之間狀態變量的聯系,形成內部聯系緊密,而與其他子系統只存在著松散的聯系
- 銀行賬戶例子
balance 100
;: (withdraw 25)
75
;: (withdraw 25)
50
;: (withdraw 60)
Not enough
;: (withdraw 15)
35
(define (make-account balance) (define (withdraw amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")) (define (deposit amount) (set! balance (+ balance amount)) balance) (define (dispatch m) (cond ((eq? m 'withdraw) withdraw) ((eq? m 'deposit) deposit) (else (error "Unknown request -- MAKE-ACCOUNT" m)))) dispatch) ;: (define acc (make-account 100)) ;: ((acc 'withdraw) 50) ;: ((acc 'withdraw) 60) ;: ((acc 'deposit) 40) ;: ((acc 'withdraw) 60)
- 引進賦值帶來的利益
蒙特卡洛模擬例子
6/Π2是隨機選取兩個整數之間沒有公共因子的概率
使用了隨機數生成器:內部擁有狀態變量
(define (estimate-pi trials) (sqrt (/ 6 (monte-carlo trials cesaro-test)))) (define (cesaro-test) (= (gcd (rand) (rand)) 1)) (define (monte-carlo trials experiment) (define (iter trials-remaining trials-passed) (cond ((= trials-remaining 0) (/ trials-passed trials)) ((experiment) (iter (- trials-remaining 1) (+ trials-passed 1))) (else (iter (- trials-remaining 1) trials-passed)))) (iter trials 0))
不使用隨機數生成器
(define (estimate-pi trials)
(sqrt (/ 6 (random-gcd-test trials random-init))))
(define (random-gcd-test trials initial-x)
(define (iter trials-remaining trials-passed x)
(let ((x1 (rand-update x)))
(let ((x2 (rand-update x1)))
(cond ((= trials-remaining 0)
(/ trials-passed trials))
((= (gcd x1 x2) 1)
(iter (- trials-remaining 1)
(+ trials-passed 1)
x2))
(else
(iter (- trials-remaining 1)
trials-passed
x2))))))
(iter trials 0 initial-x))
雖然現在還沒有看出非常大的問題,但是其中已經破壞了程序的模塊性,而且暴露本應該是內部的狀態變量。而之前的程序反應出的是一個復雜的計算性過程,其他部分都像在隨著時間不斷變化,並且隱藏起隨時間變化的內部,而這就需要局部變量去模擬系統的狀態,並用賦值來模擬它們的變化。增強模塊性
- 引進賦值的代價
引進了賦值可以去模擬系統的變化,但是這也迫使我們需要引進新的計算模型,因為代換模型已經不適用了。如果我們不使用賦值,以同樣參數對同一過程的兩次求值可以產生同樣的的結果,這樣的就稱為函數式程序設計
(define (make-decrementer balance) ;; 函數式編程,所以兩次調用並不能改變狀態
(lambda (amount)
(- balance amount)))
- 同一和變化
一旦我們將變化引進了我們的計算模型,首先考慮兩個物體實際上同一的概念
;: (define D1 (make-decrementer 25))
;: (define D2 (make-decrementer 25))
如果我們說D1 D2是同一的是可接受的,因為調用它並不會改變其內部狀態
;: (define W1 (make-simplified-withdraw 25))
;: (define W2 (make-simplified-withdraw 25))
但是W1 W2很顯然就不是同一的了
如果一個語言支持在表達式裏“同一的東西可以替換”,那麽久稱這個語言是具有引用透明性的
我們只能通過改變一個對象,去觀察另一個對象是否發生變化,以此來判斷這兩個是不是同一的,但如果不能通過觀察對象兩次,看看一次觀察中看到的某些對象性質是否與另一次不同,我們又怎麽能清楚一個對象是否變化了呢?所以如果沒有有關同一的某些先驗觀念,我們也就不能確定變化,而不能看到變化久不能確定同一性
1)
;: (define peter-acc (make-account 100))
;: (define paul-acc (make-account 100))
;:
2)
;: (define peter-acc (make-account 100))
;: (define paul-acc peter-acc)
在第一種情況下,很顯然這兩個是不同對象,但在2中,修改一個對象也就會修改另外一個對象,所以在構造計算模型的時候,就很容易引起混亂,比如在面向對象中有關對象的傳遞,同一或許有點讓人迷惑。但是如果我們保證絕不修改數據對象,那麽有關同一的概念就又不同了,就可以將一個數據對象完全看作是由其片段組成的了。在有理數中可以看作它是由分子分母組成的,所以如果修改了它的分字或者分母,它就不在是一個同一對象了。但是對於銀行賬戶,如果你改變了它的賬戶,它依舊是同一對象。
- 命令式函數設計的缺陷
在命令式函數中廣泛使用賦值,這就會引進一個復雜的問題,就是賦值的順序,狀態有關時間的變化
這一節主要是在需要一種更好的組織系統的設計方式後,一種是面向對象的方式,一種是流的方式,在基於面向對象的基礎上引入了局部變量和賦值來描述計算模型的狀態,這樣的好處的是使程序更加的具有模塊化,在每個模塊中都有自己的局部變量去描述自身的狀態,但這其中也有代價,就是發生了同一概念的復雜性
SICP讀書筆記 3.1