作者:何巖,recreating.org出品瘤载,謝絕轉(zhuǎn)載骂澄。
閱讀SICP,站在Lisp發(fā)明者的上帝視角來思考:“我為什么要這么設(shè)計(jì)Lisp惕虑?”
0.前言:Why I design those concepts—Modularity, Objects, and State?為什么我要設(shè)計(jì)模塊化坟冲,對(duì)象和狀態(tài)這些概念?
為了溃蔫,更簡(jiǎn)單的模擬物理世界健提。
我們看世界的視角有兩種:
1)將世界看成由彼此對(duì)立的物質(zhì)對(duì)象組成,即伟叛,Object View
2)將世界看成彼此始終相互作用的信息流構(gòu)成私痹,即,Stream View
雖然真實(shí)的世界是Stream的统刮。但是如果我們?nèi)四X用Stream View很難理解和模擬試卷紊遵,所以為了簡(jiǎn)化,降低人腦負(fù)荷侥蒙,我們采用Object View來簡(jiǎn)化的模擬真實(shí)世界暗膜。
而,我們生下來被教育的也正是Object View鞭衩,對(duì)象世界觀学搜,我們都習(xí)慣于此,誤以為真實(shí)的世界就真的只是Object組成论衍,卻不知道瑞佩,還有另一種看世界的方式Stream View,而坯台,佛經(jīng)里的世界觀就是Stream View炬丸,所以很難理解,但是又那么自洽蜒蕾。
我們用代碼來對(duì)比一下Object view和Stream View稠炬,給自己找點(diǎn)感覺先:
有這樣一個(gè)需求:銀行賬戶account,取款withdraw滥搭,顯示余額balance酸纲。
1)Bank Account with Object View
(define balance 100)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
2)Bank Account with Stream View
(define (stream-withdraw balance amount-stream)
(cons-stream
balance
(stream-withdraw
(- balace (stream-car amount-stream))
(stream-cdr amount-stream))))
我只是想讓你感官體驗(yàn)一下捣鲸,對(duì)于同樣一個(gè)需求瑟匆,兩種世界觀的編碼不同,后面我們會(huì)再解釋這個(gè)例子。
這兩種視角不分好壞愁溜,而是疾嗅,要根據(jù)具體的場(chǎng)景來選擇。
WHAT - Object View的關(guān)鍵點(diǎn)是有狀態(tài)
1)Object View和Stream View的本質(zhì)不同就在于是否“有狀態(tài)”冕象,Object View認(rèn)為任何對(duì)象都有自己的狀態(tài)代承,而狀態(tài)也是對(duì)象的本質(zhì)。狀態(tài)是可以靜止的存在于時(shí)間之中的渐扮。只有當(dāng)其他對(duì)象發(fā)來干涉论悴,狀態(tài)才被改變。改變后的狀態(tài)在時(shí)間這個(gè)載體上可以繼續(xù)維持(靜態(tài)保持)墓律,所以膀估,在Object View來看,對(duì)象是大多數(shù)時(shí)間不動(dòng)的耻讽。
2)而Stream View不這么看世界察纯,Stream View認(rèn)為這個(gè)世界不存在時(shí)間,也就不存在靜止不動(dòng)的所謂的“狀態(tài)”针肥,世界里的所有事物都是一刻不停的在運(yùn)動(dòng)中饼记,哪怕那些看上去沒有變化的石頭,在量子視角來看慰枕,每個(gè)組成石頭的最小單元都在隨機(jī)的量子態(tài)運(yùn)動(dòng)具则。所以,時(shí)間并不存在具帮,時(shí)間概念只是對(duì)于世界中的事物運(yùn)行的虛幻感知乡洼,是一種interface。我們抓不住時(shí)間匕坯,也看不見時(shí)間束昵,我們只能用鐘表的機(jī)械運(yùn)轉(zhuǎn)(變化)來模擬一種根本不存在的“時(shí)間”。所以葛峻,真實(shí)的世界一直都只有一刻锹雏,就是現(xiàn)在,沒有過去也沒有未來术奖,只有一刻礁遵,在這一刻上,世界在運(yùn)行采记,就好比一個(gè)人站在此刻站在刀鋒上佣耐,一直在做著動(dòng)作,卻沒有失去平衡掉下來唧龄,世界就是一個(gè)站在刀鋒上平衡運(yùn)行的人兼砖。
3)Object View中對(duì)于“有狀態(tài)”的另一層意思是,對(duì)象之間是獨(dú)立的,只有相互發(fā)消息的時(shí)候才會(huì)改變彼此的狀態(tài)讽挟。
4)而Stream View認(rèn)為時(shí)間萬物都是相互作用的辱揭,而且是持續(xù)的在互相作用中哲虾,這種復(fù)雜程度根本無法用計(jì)算機(jī)建模,也就無法完全客觀模擬。所以才無法被人理解熟呛,也不適合工程化临扮。但是诫惭,我們需要模擬窃祝,只能降低清晰度,模糊的構(gòu)建事物之間的關(guān)系众旗。
而竟贯,能夠感知到時(shí)間萬物都在相互效力的設(shè)備就是我們的心。
5)在Object View中逝钥,為了保存狀態(tài)屑那,我們需要?jiǎng)?chuàng)造一個(gè)存儲(chǔ)空間,這就是environment艘款。衍生出來的概念體系是:environment model.
6)人們的焦慮/興奮本質(zhì)上只不過是為了增加/保護(hù)那個(gè)虛幻的“狀態(tài)”STATE持际。財(cái)富是個(gè)State,地位是個(gè)State哗咆,總之可以用一個(gè)數(shù)字簡(jiǎn)單代表的就是State蜘欲,而這個(gè)評(píng)估因?yàn)閬碜杂谒耍晕覀儾拍敲丛诤跛松渭恚驗(yàn)槿绻屗擞憛捓逊荩覀兊膕tate就會(huì)很低,我們的自我存在感就會(huì)很低年碘。這就是受制于人澈歉。
7)但是有些智者活在Stream View世界觀下,他們不在乎已經(jīng)取得的成績(jī)屿衅,他們更關(guān)注每一天的做事的行為埃难,有沒有比之前更好,即關(guān)注改變涤久。改變就是掌控感涡尘,從掌控感中直接獲得快感和自我意義,這就不再受制于人响迂,而只取決于自己的評(píng)估考抄,即,面對(duì)當(dāng)下情況蔗彤,老我如何做川梅,新我如何做疯兼,改變就這么被感知到了,成就感隨即產(chǎn)生挑势。
所以說镇防,Object View下的人關(guān)注存量(數(shù)值的多少)啦鸣,Stream View下的人關(guān)注改變(變化本身潮饱,增量,節(jié)奏诫给,信息)香拉,這也是有限和無限游戲的本質(zhì)不同。
所以中狂,想要轉(zhuǎn)變成Stream View世界觀凫碌,信息是個(gè)關(guān)鍵詞。
關(guān)注獲得的信息胃榕,而不要關(guān)注身外之物盛险。
Chapter 3.1 Assignment and Local State
1)為了,可以操控State勋又,需要我們?cè)O(shè)計(jì)一種新概念苦掘,即,Assignment賦值楔壤。
前兩章鹤啡,變量不可以被修改,只能創(chuàng)建變量蹲嚣。所有procedure都是無狀態(tài)的递瑰,即,只要參數(shù)不變隙畜,不管調(diào)用多少次抖部,結(jié)果也不變。
2)所以议惰,真實(shí)的世界本不存在狀態(tài)您朽,真實(shí)世界是都是由無狀態(tài)的procedure疊加構(gòu)成,我們看到的每一個(gè)“色”的背后都疊加了我們無法感知到的超級(jí)復(fù)雜的procedure相互效力换淆。所以哗总,真實(shí)世界是一個(gè)無法想象數(shù)量級(jí)的并發(fā)系統(tǒng)。
3)因?yàn)橛?jì)算機(jī)系統(tǒng)資源有限倍试,無法處理過多的的并發(fā)讯屈,只能簡(jiǎn)單的模擬“色”而不能模擬產(chǎn)生色的所有疊加因素procedure。
4)而县习,bitcoin的utxo模型則是stream view下的產(chǎn)物涮母,交易永遠(yuǎn)在疊加谆趾,交易不可改,每筆交易都是無狀態(tài)的叛本。所以沪蓬,bitcoin更加符合宇宙模型。
5)關(guān)于狀態(tài)来候,是在Object之中的跷叉,所以叫做Local State
6)為了表示對(duì)State的操作,我們?cè)黾右粋€(gè)操作符:SET!
1.為了解釋得更清楚营搅,我們用銀行取款的例子來講解云挟。
— Chapter 3.1.1 Local State Variables
假設(shè)我們初始余額為100,我們希望的效果是转质,對(duì)于interface withdraw 每次調(diào)用之后园欣,顯示的余額都發(fā)生變化。
(withdraw 25)
75
(withdraw 25)
50
(withdraw 60)
"Insufficient funds"
(withdraw 15)
35
同樣的參數(shù)休蟹,同樣的procedure沸枯,每次調(diào)用后結(jié)果不同,這就是和前兩章不同所在赂弓。
HOW - withdraw的實(shí)現(xiàn)代碼如下:
(define balance 100)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
;;Decrementing balance is accomplished by the expression
(set! balance (- balance amount))
;;This uses the set! special form, whose syntax is
(set! <name> <new-value>)
Here <name> is a symbol and <new-value> is any expression. Set! changes <name> so that its value is the result obtained by evaluating <new-value>.
SET!的本質(zhì)就是其他語言中的等號(hào)“=”绑榴,可以看出“=”只是一個(gè)語法糖。從SICP里我們可以看到各種語法糖背后的本質(zhì)拣展,即彭沼,獲得物理視角。
Why - I need “begin”?
因?yàn)楸赴#琤egin類似一個(gè)時(shí)間鎖姓惑,在begin這個(gè)block中,時(shí)間是單線流轉(zhuǎn)的按脚。
(begin <exp1> <exp2> ... <expk>)
the value of the final expression <expk> to be returned as the value of the entire begin form.
HOW - 升級(jí)withdraw為具有Local State于毙,為了讓balance變成私有,不被其他procedure引用辅搬。
(define new-withdraw
(let ((balance 100))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
這就是一個(gè)閉包唯沮。
HOW - 升級(jí)為一個(gè)完整的賬戶操作模塊
(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) ;;return interface procedure dispatch
這就是一個(gè)Object,dispatch就是interface堪遂,唯一入口介蛉,因?yàn)閜rocedure只能返回一個(gè)procedure,所以只能返回dispatch作為分發(fā)入口溶褪。這里用到的概念是消息驅(qū)動(dòng)message-passing style.
下面是演示如何使用make-account
(define acc (make-account 100))
((acc 'withdraw) 50)
50
((acc 'withdraw) 60)
"Insufficient funds"
((acc 'deposit) 40)
90
((acc 'withdraw) 60)
30
;; another call to make-account
(define acc2 (make-account 100))
;; will produce a completely separate account object, which maintains its own local balance.
;; Why? 因?yàn)闀?huì)分配獨(dú)立的frame
Exercise 3.1 實(shí)現(xiàn)make-accumulator,累加
設(shè)計(jì)效果如下:
(define A (make-accumulator 5))
(A 10)
15
(A 10)
25
代碼實(shí)現(xiàn)如下:
(define (make-accumulator x)
(let ((localvale x))
(lambda (y)
(begin
(set! localvalue (+ localvalue y))
localvalue))))
Exercise 3.2 實(shí)現(xiàn)make-monitored币旧,監(jiān)控f的執(zhí)行次數(shù)
為了測(cè)試procedure,我們要實(shí)現(xiàn)一個(gè)第三方的procedure:make-monitored猿妈。
每次procedure執(zhí)行一次吹菱,計(jì)數(shù)器counter就加一巍虫。
我們使用消息驅(qū)動(dòng),如果消息是how-many-calls?則返回計(jì)數(shù)器的值
如果消息是reset-count則重置counter為0.
其他消息則返回procedure的值鳍刷。
設(shè)計(jì)效果如下:
(define s (make-monitored sqrt))
(s 100)
10
(s 'how-many-calls?)
1
實(shí)現(xiàn)思路如下:
1)如何感知到procedure的執(zhí)行占遥?換句話說如果是一個(gè)遞歸procedure 如何計(jì)數(shù)?
2)或者我理解錯(cuò)了输瓜,計(jì)數(shù)不是要記錄procedure的遞歸次數(shù)瓦胎,而是make-monitored被調(diào)用的次數(shù)。這樣是可以通過不侵入procedure的代碼來實(shí)現(xiàn)計(jì)數(shù)前痘。
具體代碼實(shí)現(xiàn)如下:
(define (make-monitored f)
(let ((count 0))
(define (how-many-calls?)
(count))
(define (reset-count)
(set! count 0))
(define (other)
(set! count (+ 1 count))
f)
(define (dispatch m)
(cond
((eq? m 'how-many-calls?)
how-may-calls?)
((eq? m 'reset-count)
reset-count)
(else
other)))
dispatch))
Exercise 3.3 實(shí)現(xiàn)賬戶密碼功能
效果如下:
(define acc (make-account 100 'secret-password))
((acc 'secret-password 'withdraw) 40)
60
((acc 'some-other-password 'deposit) 50)
"Incorrect password"
具體實(shí)現(xiàn)代碼如下:
(define (make-account balance password)
(define (withdraw amount)
(if (<= amount balance)
(begin
(SET! balance (- balance x)
balance)
(else
("Insufficient funds")))
(define (deposit amount)
(SET! balance (+ balance amount)))
(define (dispatch pwd m)
(cond
((eq? pwd password)
(cond
((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknow request -- MAKE-ACCOUNT" m))))
(else "Incorrect password")))
dispatch)
Exercise 3.4 升級(jí)上一個(gè)例子凛捏,加入一個(gè)入?yún)all-the-cops担忧,即芹缔,如果密碼錯(cuò)誤超過7次則調(diào)用call-the-cops的procedure
代碼如下:
(define (make-account balance password call-the-cops)
(let ((incorrectTimes 0))
(define (withdraw amount)
(if (<= amount balance)
(begin
(SET! balance (- balance x)
balance)
(else
("Insufficient funds")))
(define (deposit amount)
(SET! balance (+ balance amount)))
(define (dispatch pwd m)
(cond
((eq? pwd password)
(cond
((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknow request -- MAKE-ACCOUNT" m))))
(else
(begin
(SET! incorrectTimes (+ 1 incorrectTimes)
(error "Incorrect password, incorrectTimes now is:" incorrectTimes)))))
dispatch))
2.What is the benefits of Introducing Assignment? 賦值語句的好處是什么?
— Chapter 3.1.2 The Benefits of Introducing Assignment
Assignment can create random number瓶盛。
這個(gè)是pure procedure搞不定的事情最欠。
WHAT - 隨機(jī)數(shù)的本質(zhì)是什么?
不可重復(fù)性惩猫。
而pure procedure是可重復(fù)的芝硬。
3.What is the costs of Introducing Assignment?引入賦值語句的成本是什么?
— Chapter 3.1.3 The Costs of Introducing Assignment
assignment的引入轧房,procedure不再等價(jià)于數(shù)學(xué)函數(shù)mathematical functions
前兩章拌阴,沒有assignment之前,所有的procedure都符合面向函數(shù)編程:functional programming
我們來對(duì)比一下奶镶,這兩種編程方式的不同效果:
1)assignment:相同的參數(shù)迟赃,不同的結(jié)果
(define (make-simplified-withdraw balance)
(lambda (amount)
(set! balance (- balance amount))
balance))
(define W (make-simplified-withdraw 25))
(W 20)
5
(W 10)
-5
2)not use set! : 相同的參數(shù),相同的結(jié)果
(define (make-decrementer balance)
(lambda (amount)
(- balance amount)))
(define D (make-decrementer 25))
(D 20)
5
(D 20)
5
(D 10)
15
我們用substitution model 來解釋make-decrementer的運(yùn)行
((make-decrementer 25) 20)
((lambda (amount) (- 25 amount)) 20)
(- 25 20)
5
如果我們用類似的substitution來分析make-simplified-withdraw
((make-simplified-withdraw 25) 20)
((lambda (amount) (set! balance (- 25 amount)) 25) 20)
(set! balance (-25 20)) 25
25
WHY - I need Environment model
如果用類似的substitution model則返回了錯(cuò)誤答案25.
因?yàn)槌д颍趕et!作用之前纤壁,balance的值就已經(jīng)被替換了。正確的做法應(yīng)該是等SET!之后再替換balance的值捺信。
所以酌媒,引入了SET!的procedure就不再可以使用substitution model
而要使用一種新的model,即迄靠,Environment model.
SET!的關(guān)鍵點(diǎn)秒咨,就是需要有一個(gè)地方來存儲(chǔ)狀態(tài)STATE,并且執(zhí)行SET!之后可以替換掌挚。Enviroment就是這個(gè)空間雨席。
Exercise 3.7 實(shí)現(xiàn)make-joint
效果如下:
(define paul-acc
(make-joint peter-acc 'open-sesame 'rosebud))
實(shí)現(xiàn)思路:
方案1:
1)refer make-account
2)改造make-account用一個(gè)list來存儲(chǔ)多個(gè)密碼
方案2:
1)不用refer make-account
2)只是在make-joint中用Higer-Order插入一層映射層,將pwd1和pwd2做映射疫诽,如果pwd2等于之前的pwd2則映射到pwd1在調(diào)用acc1舅世,剩下的事全部有acc1去做旦委,這樣的好處是不侵入代碼,只是增加一個(gè)代理層來解決雏亚。
(define (make-joint acc1 pwd1 pwd2)
(lambda (password m) ;;lambda的意義:代表返回的procedure接受參數(shù)
(cond
((eq? m pwd2)
(acc1 pwd1 m))
(else
"Incorrect the second password"))
這里可以看到Higher-Order的意義缨硝,加入一個(gè)lambda作為代理procedure來接受本來應(yīng)該返回的procedure相同的參數(shù)。這樣罢低,使用者就以為自己在使用本應(yīng)該返回的procedure查辩。其實(shí)只是外面包裹了一層邏輯的匿名procedure。
Chapter3.2 The Environment Model of Evaluation
1.Why I design The Environment Model of Evaluation?為什么我要設(shè)計(jì)環(huán)境求值模型网持?
因?yàn)橐说海琒ET!讓過去的Substitution Model不再適用,所以功舀,需要開辟一個(gè)新Model萍倡。
WHAT - Model從抽象上來看是什么?
規(guī)則辟汰,世界運(yùn)轉(zhuǎn)的規(guī)律列敲,選擇不同的視角來理解世界,就是選擇不同的規(guī)則帖汞。
在一定范圍內(nèi)戴而,兩種規(guī)則都適用,都是自洽體系翩蘸。甚至不那么準(zhǔn)確的規(guī)則更實(shí)用所意。
兩種規(guī)則對(duì)于applying的描述對(duì)比:what is meant by applying a procedure to arguments
1)Substitution model of evaluation: To apply a compound procedure to arguments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument.
2)Environment model of evaluation: In the presence of assignment, a variable can no longer be considered to be merely a name for a value. Rather, a variable must somehow designate a “place” in which values can be stored. In our new model of evaluation, these places will be maintained in structures called environments.
本質(zhì)的區(qū)別在于:變量變得不同,前者是a name for a value催首,后者是designate a “place” in which values can be stored.
2.What is the Rules for Evaluation
— Chapter 3.2.1 The Rules for Evaluation
Substitution model
1)Evaluate the subexpressions of the combination.
2)Apply the value of the operator subexpression to the values of the operand subexpressions
3)To apply a compound procedure to arguments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument.
Environment model
1)A procedure object is applied to a set of arguments by constructing a frame, binding the formal parameters of the procedure to the arguments of the call, and then evaluating the body of the procedure in the context of the new environment constructed. The new frame has as its enclosing environment the environment part of the procedure object being applied.
2)A procedure is created by evaluating a lambda expression relative to a given environment. The resulting procedure object is a pair consisting of the text of the lambda expression and a pointer to the environment in which the procedure was created.
兩者本質(zhì)的區(qū)別在于扶踊,apply的時(shí)候,前者是replace翅帜,后者是bind姻檀。即,to apply a procedure to argument, create a new environment containing a frame that binds the parameters to the values of the arguments.
HOW - 用square來示意
(define (square x) (* x x))
;;等價(jià)于
(define square
(lambda (x) (* x x)))
1)A procedure is created如下圖:shows the result of evaluating this define expression.
[圖片上傳失敗...(image-cf6df7-1602681248227)]
2)A procedure object is applied如下圖:shows the procedure be applied: (square 5)
[圖片上傳失敗...(image-61528c-1602681248227)]
Define就像是模型涝滴,Apply就像是實(shí)例化
3.Applying Simple Procedures
— Chapter 3.2.2 Applying Simple Procedures
為了實(shí)踐Environment Model绣版,我們定義如下procedures
(define (square x)
(* x x))
(define (sum-of-squares x y)
(+ (square x) (square y)))
(define (f a)
(sum-of-squares (+ a 1) (* a 2)))
下圖為Procedures be created
[圖片上傳失敗...(image-349e5-1602681248227)]
下圖為Procedure objects be applied:(f 5)
[圖片上傳失敗...(image-621b6e-1602681248227)]
Exercise 3.9 : 用Environment Model來解釋factorial procedure
;; 1.recursive version
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
思路:
首先來看recursive version
1)Procedure create,parameters:n歼疮,body:(if (= n 1)…(factorial…)
2)Procedure create杂抽,global environment中將factorial bind到procedure object
3)Procedure object be applied,(factorial 6)韩脏,創(chuàng)建一個(gè)E1缩麸,n:6,evaluate body:
(if (= 6 1)
1
(* 6 (factorial (- 6 1)))))
(* 6 (factorial 5))))
(* 6 (body... 5))))
4)Procedure object be applied赡矢,(factorial 5)杭朱,創(chuàng)建E2阅仔,n:5,evaluate body
(* 6 (* 5 (body... 4)))))
…
直到
(* 6 (* 5 (* 2 1)))))
再來看interactive version
;; 2.iteractive version
(define (factorial n)
(fact-iter 1 1 n))
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter
(* counter product)
(+ counter 1)
max-count)))
1)Procedure create弧械,env bind factorial和fact-iter
2)Procedure object applied八酒,E1 中n:6 , (fact-iter 1 1 6)
3)Procedure object applied刃唐,E2中product:1, counter:1, max-count:6,
(fact-iter (* 1 1) (+ 1 1) 6)
(fact-iter 1 2 6)
4)Procedure object applied羞迷,E3中product:1, counter:2, max-count:6
(fact-iter (* 1 2) (+ 2 1) 6)
(fact-iter 2 3 6)
…直到 counter > max-count
4.HOW - 如何用frames來存儲(chǔ)Local State
Environment Model就是為了處理Local State而創(chuàng)造的,所以之前我們演示了簡(jiǎn)單的部分画饥,即衔瓮,本來可以用Substitution Model搞定的simply procedure,下面我們進(jìn)入真正的主角抖甘,處理擁有assignment的procedure
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")))
(define W1 (make-withdraw 100))
(W1 50)
50
1)Result of defining make-withdraw in the global environment
[圖片上傳失敗...(image-9e3b13-1602681248227)]
2)Result of evaluating (define W1 (make-withdraw 100))
We see that E1 serves as the “place” that holds the local state variable for the procedure object W1
[圖片上傳失敗...(image-4bad0d-1602681248227)]
- Environment created by applying the procedure object W1
[圖片上傳失敗...(image-7dbf61-1602681248227)]
4)Environments after the call to W1
When the set! is executed, the binding of balance in E1 is changed.
本質(zhì)上热鞍,擁有SET!和不用SET!在構(gòu)建frame層面沒有變化,唯一變化的點(diǎn)只是SET!可以去改之前frame中的值单山。而沒有SET!的只有創(chuàng)建新的E里面再綁定新的value碍现。
[圖片上傳失敗...(image-4b100a-1602681248227)]
5)Using (define W2 (make-withdraw 100)) to create a second object.
[圖片上傳失敗...(image-6e67a2-1602681248227)]
Exercise 3.10 如果用了let幅疼,模型會(huì)如何構(gòu)建
答案是多了一層E米奸,因?yàn)閘et的本質(zhì)就是多包了一層lambda
(define (make-withdraw initial-amount)
(let ((balance initial-amount))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
(let ((<var> <exp>)) <body>)
;;is interpreted as an alternate syntax for
((lambda (<var>) <body>) <exp>)
(define (make-withdraw initial-amount)
(lambda (balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
) (initial-amount)
多了一層E,如下圖:
[圖片上傳失敗...(image-c76fdb-1602681248227)]
5. Why I need Internal Definitions爽篷? 我為什么需要內(nèi)部定義悴晰?
— Chapter 3.2.4 Internal Definitions
因?yàn)椋琲nternal Definitions更加模塊化,不污染global environment
(define (sqrt x)
(define (good-enough? guess)
(< (abs (- (square guess) x)) 0.001))
(define (improve guess)
(average guess (/ x guess)))
(define (sqrt-iter guess)
(if (good-enough? guess)
guess
(sqrt-iter (improve guess))))
(sqrt-iter 1.0))
[圖片上傳失敗...(image-405e15-1602681248227)]
Internal Definitions的優(yōu)點(diǎn):
1)The names of the local procedures do not interfere with names external to the enclosing procedure, because the local procedure names will be bound in the frame that the procedure creates when it is run, rather than being bound in the global environment. (不運(yùn)行則指存在于Procedure Object的body部分中)
2)The local procedures can access the arguments of the enclosing procedure, simply by using parameter names as free variables. This is because the body of the local procedure is evaluated in an environment that is subordinate to the evaluation environment for the enclosing procedure.
Chapter 3.3 Modeling with Mutable Data
1.Why I import this concept that Modeling with Mutable Data?
因?yàn)橹鸸ぃ肕utable Data來代表State铡溪。
又因?yàn)椋琒tate需要修改泪喊,所以Data就需要變成Mutable Data棕硫。
而,之前的Data都只有constructor和selector無法修改袒啼,如果變化只有創(chuàng)建哈扮。
WHY - 為什么需要Mutable Data來代表狀態(tài)?
因?yàn)轵驹伲K化的思想滑肉,簡(jiǎn)單的Data無法足夠好的模擬真實(shí)的復(fù)雜對(duì)象的狀態(tài)。
也就是說摘仅,真實(shí)世界的對(duì)象的狀態(tài)很復(fù)雜靶庙,需要用復(fù)雜的Data來模擬,又因?yàn)镾tate的本質(zhì)就是要可修改SET!娃属,所以Data要稱為Mutable Data六荒。
例如:對(duì)于如下復(fù)雜數(shù)據(jù)的修改:
1)List 护姆,通過set-car! 和 set-cdr!進(jìn)行修改
2)Queue,通過insert-queue! 和 delete-queue!進(jìn)行修改
3)Table掏击,通過insert!進(jìn)行修改
這些修改操作符就叫做mutators
這些Data objects就叫做mutable data objects
2.HOW - 如何讓List變成可修改呢签则?
— Chapter 3.3.1 Mutable List Structure
通過set-car!和set-cdr!
set-car!和set-cdr!的本質(zhì)就是操縱Pair中的指針Pointer
數(shù)據(jù)早已存在,改變指針其實(shí)是很小的動(dòng)作铐料。
過去不用的數(shù)據(jù)渐裂,會(huì)有GC回收。
類比真實(shí)世界钠惩,轉(zhuǎn)念就是改變指針柒凉。
真正改變命運(yùn)不需要我們費(fèi)勁的去構(gòu)建數(shù)據(jù),而只需要輕松的改變指針篓跛,將底層假設(shè)一變膝捞,我們自己的世界就變化了。
影響我們的不是世界愧沟,而是我們對(duì)世界的看法蔬咬。
而,傳統(tǒng)的觀念是沐寺,改變的本質(zhì)是改變數(shù)據(jù)林艘,或者創(chuàng)造新的數(shù)據(jù)。這樣就很費(fèi)勁混坞。
其實(shí)好的數(shù)據(jù)已經(jīng)存在狐援,只需要我們免費(fèi)的改變指針指向它們。
這本質(zhì)上需要對(duì)Data和Pointer的理解更加深入究孕。
Pointer代表了關(guān)系啥酱。
Data代表了虛擬的實(shí)體。
真正起到?jīng)Q定作用的反而是看起來更輕的關(guān)系Pointer厨诸。
WHAT - Mutation is just assignment
對(duì)比 Data is just Procedure
Mutation的本質(zhì)就是SET!
之前對(duì)于Pair的實(shí)現(xiàn)镶殷,如下:
(define (cons x y)
(define (dispatch m)
(cond
((eq? m 'car) x)
((eq? m 'cdr) y)
(else (error "Undefined operation -- CONS" m))))
dispatch)
(define (car z) (z 'car))
(define (cdr z) (z 'cdr))
改造后的Pair可以實(shí)現(xiàn)修改,如下:
(define (cons x y)
(define (set-x! v) (set! x v))
(define (set-y! v) (set! y v))
(define (dispatch m)
(cond
((eq? m 'car) x)
((eq? m 'cdr) y)
((eq? m 'set-car!) set-x!)
((eq? m 'set-cdr!) set-y!)
(else (error "Undefined operation -- CONS" m))))
dispatch)
(define (car z) (z 'car))
(define (cdr z) (z 'cdr))
(define (set-car! z new-value)
((z 'set-car!) new-value)
z)
(define (set-cdr! z new-value)
((z 'set-cdr!) new-value)
z)
3.HOW - 如何構(gòu)建Queues
Queue的設(shè)計(jì)效果如下:
Operation Resulting Queue
(define q (make-queue))
(insert-queue! q 'a) a
(insert-queue! q 'b) a b
(delete-queue! q) b
(insert-queue! q 'c) b c
(insert-queue! q 'd) b c d
(delete-queue! q) c d
Queue的設(shè)計(jì)如下:
;; a consturctor:
(make-queue)
;; two selectors:
(empty-queue? <queue>)
(front-queue <queue>)
;; two mutators:
(insert-queue! <queue> <item>)
(delete-queue! <queue>)
Queue的設(shè)計(jì)圖微酬,如下:
[圖片上傳失敗...(image-421231-1602681248227)]
WHAT - QUEUE的設(shè)計(jì)思想符合分層思想绘趋,通過增加一層控制層來實(shí)現(xiàn)效率的增加。如果用少一層的list來實(shí)現(xiàn)也可以得封,不過效率會(huì)變低埋心,復(fù)雜度微O(n),而增加一層控制層忙上,復(fù)雜度就變成了O(1)
HOW - 啟發(fā)是什么拷呆?
資源都在,只需要重新設(shè)計(jì)一下,組織一下指針茬斧,神奇的事情就會(huì)發(fā)生腰懂。
這就是定位的本質(zhì)。
真正的控局者是指針控制者项秉,或者稱為指針設(shè)計(jì)者绣溜。
QUEUE的具體實(shí)現(xiàn)代碼如下:
;; 控制層pointer
(define (front-ptr queue) (car queue))
(define (rear-ptr queue) (cdr queue))
(define (set-front-ptr! queue item) (set-car! queue item))
(define (set-rear-ptr! queue item) (set-cdr! queue item))
;; 對(duì)body的控制
(define (empty-queue? queue) (null? (front-ptr queue)))
(define (make-queue) (cons '() '()))
(define (front-queue queue)
(if (empty-queue? queue)
(error "FRONT called with an empty queue" queue)
(car (front-ptr queue))))
(define (insert-queue! queue item)
(let ((new-pair (cons item '())))
(cond
((empty-queue? queue)
(set-front-ptr! queue new-pair)
(set-rear-ptr! queue new-pair)
queue)
(else
(set-cdr! (rear-ptr queue) new-pair)
(set-rear-ptr! queue new-pair)
queue))
(define (delete-queue! queue)
(cond
((empty-queue? queue)
(error "DELETE! called with an empty queue" queue))
(else
(set-front-ptr! queue (cdr (front-ptr queue)))
queue)))
4.HOW - 如何構(gòu)建Table
one-dimensional table單維表
a:1
b:2
c:3
設(shè)計(jì)圖如下:
[圖片上傳失敗...(image-6115fc-1602681248227)]
代碼實(shí)現(xiàn)如下:
(define (lookup key table)
(let ((record (assoc key (cdr table))))
(if record
(cdr record)
false)))
(define (assoc key records)
(cond
((null? records) false)
((equal? key (caar records)) (car records))
(else (assoc key (cdr records)))))
(define (insert! key value table)
(let ((record (assoc key (cdr table))))
(if record
(set-cdr! record value)
(set-cdr! table
(cons (cons key value) (cdr table)))))
'ok)
(define (make-table)
(list '*table*))
Two-dimensional tables 二維表
math:
+: 43
-: 45
*: 42
letters:
a: 97
b:98
設(shè)計(jì)圖如下:
[圖片上傳失敗...(image-b15af3-1602681248227)]
我們可以看到通過Pair的自由組合,可以構(gòu)建任意的復(fù)雜Data
本質(zhì)上娄蔼,自由組合基于Pair的粘性怖喻,即,最小可組合性岁诉。
具體代碼實(shí)現(xiàn):
(define (lookup key-1 key-2 table)
(let ((subtable (assoc key-1 (cdr table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(cdr record)
false))
false)))
(define (insert! key-1 key-2 value table)
(let ((subtable (assoc key-1 (cdr table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(set-cdr! record value)
(set-cdr! subtable
(cons (cons key-2 value)
(cdr subtable)))))
(set-cdr! table
(cons (list key-1 (cons key-2 value))
(cdr table)))))
'ok)
HOW - creating local tables
(define (make-table)
(let ((local-table (list '*table*)))
(define (lookup key-1 key-2)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(cdr record)
false))
false)))
(define (insert! key-1 key-2 value)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(set-cdr! record value)
(set-cdr! subtable
(cons (cons key-2 value)
(cdr subtable)))))
(set-cdr! local-table
(cons (list key-1
(cons key-2 value))
(cdr local-table)))))
'ok)
(define (dispatch m)
(cond
((eq? m 'lookup-proc) lookup)
((eq? m 'insert-proc!) insert!)
(else (error "Unknown operation -- TABLE" m))))
dispatch))
;; how to use it
(define operation-table (make-table))
(define get (operation-table 'lookup-proc))
(define put (operation-talbe 'insert-proc!))