作者:何巖牲芋,recreating.org出品剑梳,謝絕轉(zhuǎn)載呜达。
閱讀SICP谣蠢,站在Lisp發(fā)明者的上帝視角來思考:“我為什么要這么設(shè)計(jì)Lisp?”
0.前言:Why I design the concept of Data? 為什么要設(shè)計(jì)Data這個(gè)概念查近?Procedure不夠用嗎眉踱?
因?yàn)椋琧ontrol complexity霜威。
因?yàn)檠福四X習(xí)慣Object View,需要讓人們產(chǎn)生Data這個(gè)Object想象體侥祭。這樣可以降低思考復(fù)雜度。
因?yàn)榍牙澹琌bject/Data的抽象思想是Black-Box Abstraction矮冬,也可以稱為modularity。modularity可以降低復(fù)雜度次哈,因?yàn)樘ナ穑琺odularity減少了關(guān)系發(fā)生的數(shù)量。
因?yàn)橐ぶ停y(tǒng)一視角琼牧,抽象思維恢筝,type的本質(zhì)。例如:Java中的接口
Procedure is Stream View
Data is Object View
1.How I design the implementation of Data?我如何設(shè)計(jì)Data的實(shí)現(xiàn)巨坊?
— Chapter 2.1.3 What Is Meant by Data?
我將用Procedures來虛擬出Data撬槽。
Data對(duì)外提供可以被感知到的是一層interface,interface的本質(zhì)就是procedure趾撵。
用戶就會(huì)想象侄柔,Data貌似真的存在。
其實(shí)占调,那些作為interface的procedure也是由更底層的procedure組成的暂题。
例如,用procedure來模擬Pairs的存在
為了欺騙使用Pair的人究珊,我會(huì)提供Cons/Car/Cdr這三個(gè)Procedures作為interface薪者。
使用Pair的人會(huì)這么操縱Pair暴露的interface:cons/car/cdr:
=>(define x (cons 1 2) ;構(gòu)建一個(gè)叫做x的Pair,由1和2組成
=>(car x) ;獲得Pair中存儲(chǔ)的第一個(gè)元素
1
=>(cdr x) ;獲得Pair中存儲(chǔ)的第二個(gè)元素
2
我們來看看這三個(gè)Procedures的內(nèi)部究竟有沒有Data的存在剿涮?
方案1:用Procedure模擬Data
(define (cons x y)
(define (dispatch m)
(cond
((= m 0) x)
((= m 1) y)
(else (error "Argument not 0 or 1 -- CONS" m))))
dispatch)
(define (car z) (z 0))
(define (cdr z) (z 1))
方案2:用Procedure模擬Data言津,上一個(gè)方案里還用到了數(shù)字,我們這次用純Procedure:
(define (cons x y)
(lambda (m) (m x y)))
(define (car z)
(z (lambda (p q) p)))
(define (cdr z)
(z (lambda (p q) q)))
因?yàn)獒B玻琇isp的Procedure的設(shè)計(jì)是基于Lambda演算的思想纺念,即,Procedure就是Lambda想括。
所以陷谱,Data的存在,本質(zhì)上是基于Lambda瑟蜈,更微觀的看是基于Lambda的可組裝能力烟逊,在Lambda演算中,這種組裝變形的能力铺根,稱為:Beta規(guī)約(β-reduction)
再抽象來思考宪躯,Lambda的可組裝能力來自于什么呢?
答案是位迂,來自于访雪,阿隆索·丘奇定義了一個(gè)規(guī)則/邏輯,也就是人腦的想象掂林。
這個(gè)組合的邏輯來自于哪里臣缀?
答案是,來自于泻帮,人類對(duì)宇宙規(guī)律的觀察精置,模擬出宇宙的底層規(guī)律,就是最小的元素的可組裝性锣杂。
而這個(gè)邏輯是真理嗎脂倦?不知道番宁,無法證明,所以說赖阻,第一公理只能靠相信蝶押。
所以說,計(jì)算機(jī)科學(xué)的整座大廈都懸在Lambda演算所定義出的這個(gè)邏輯想象之上政供。
Data并不“真的”存在播聪,我們能感受到的Data是它的interface(色),而它的本體卻是“空”布隔,所以說离陶,色即是空。
而Data雖然是“空”但并不是什么也沒有衅檀,只不過是沒有我們認(rèn)為的那種物質(zhì)感存在感招刨,Data的構(gòu)建還是要基于其他的procedures(色),所以說:空即是色哀军。
這里可以隱喻出沉眶,真實(shí)世界的原子并不是物質(zhì)性的存在,原子也是由非物質(zhì)的夸克組成杉适,而夸克只是一種震動(dòng)谎倔,可以將夸克理解成信息。而這個(gè)信息來自于哪里猿推?來自于一種想象片习,那就是上帝意識(shí)的想象。這種上帝意識(shí)同樣在我們每個(gè)人之中蹬叭,所以這個(gè)世界本質(zhì)上就是一個(gè)想象共同體藕咏。
Data隱喻著原子,Procedure隱喻著夸克秽五。
Data隱喻著物質(zhì)孽查,Procedure隱喻著信息。
2.我為什么要如此設(shè)計(jì)構(gòu)建Data的Framework坦喘,即selectors and constructors
— Chapter 2.1.1 Example: Arithmetic Operations for Rational Numbers
因?yàn)槊ぴ伲橄笊蟻砜矗@是一分一合瓣铣。一收一放洲胖。猶如陰陽,漩渦模型的核心坯沪,旋轉(zhuǎn)著涌現(xiàn)出豐盛的世界。
selectors:car/cdr擒滑、分腐晾、放
constructors:cons叉弦、合、收
隱喻著藻糖,Lisp的漩渦核心淹冰,Lisp的元解釋器,Meta-circular Evaluator巨柒,即樱拴,Eval/Apply
Eval就是Selectors,分洋满、放
Apply就是Constructors晶乔,合、收
所以說牺勾,抽象來看一切都是相通的正罢。
How - 根據(jù)這個(gè)構(gòu)建Data的Framework,我將如何構(gòu)建有理數(shù)Rational Numbers
思路如下:
1.設(shè)計(jì)constructors驻民,即翻具,(make-rat <n> <d>)
2.設(shè)計(jì)selectors,即回还,(numer <x>) 和 (denom <x>)
具體實(shí)現(xiàn):
利用pairs的interface來構(gòu)建Rational Numbers的interface裆泳。
所以,真正的編程都是在編寫interface而已柠硕,即工禾,面向interface編程。
(define (make-rat n d) (cons n d))
(define (numer x) (car x))
(define (denom x) (cdr x))
不要以為仅叫,make-rat直覺代理了cons帜篇,沒有添加新的邏輯,就沒有意義诫咱。有人會(huì)問為什么不直接用cons來作為有理數(shù)的interfaxe笙隙?
因?yàn)椋@是一個(gè)定位問題坎缭,需要在虛空中給有理數(shù)一個(gè)位置竟痰,哪怕有理數(shù)的構(gòu)建procedure中什么也沒做,又位置掏呼,就有概念坏快,就會(huì)有想象體的產(chǎn)生。
另外憎夷,有了位置莽鸿,有理數(shù)就有空間來加入自己的邏輯。例如實(shí)現(xiàn)分子和分母自動(dòng)歸約:
(define (make-rat n d)
(let ((g (gcd n d)))
(cons (/ n g) (/ d g))))
3.Why I design Abstraction Barriers Framework? 我為什么要提出分層思想框架?
這里祥得,是分層思想的源頭兔沃。SICP不愧為編程世界的“元典”。
因?yàn)榧都埃謱邮且环N抽象的哲學(xué)思想乒疏,它的設(shè)計(jì)思路適用于如下領(lǐng)域:Lisp解釋器的設(shè)計(jì),領(lǐng)域語言的設(shè)計(jì)(DSL)饮焦,嵌入式新語言的設(shè)計(jì)怕吴,接口的設(shè)計(jì),新類型數(shù)據(jù)的設(shè)計(jì)县踢。面向服務(wù)的設(shè)計(jì)转绷。
— Programs that use rational numbers—
Rational numbers in problem domain
— add-rat sub-rat mul-rat sub-rab —
Rational numbers as numerators and denominators
— make-rat number denom —
Rational numbers as pairs
— cons car cdr —
However pairs are implemented
本質(zhì)上,每一層都是使用下一冊(cè)提供的interface來構(gòu)建自己的邏輯殿雪,再對(duì)上一層提供自己的interface郑藏。
另一個(gè)洞見是锤窑,一種新語言就是一種Data,因?yàn)椋O(shè)計(jì)語言的思路和設(shè)計(jì)新Data的思路樣律适,抽象上來看彤蔽,他們就是一回事潭陪。
Data就是尊從了Black-Box Abstraction的思想套么。
分層思想實(shí)現(xiàn)了Black-Box Abstraction思想,但是不止于此索抓。
分層思想還提供了一種Power钧忽,即,分層協(xié)作所產(chǎn)生的強(qiáng)大Power
為了解決一類特別領(lǐng)域的業(yè)務(wù)逼肯,設(shè)計(jì)一個(gè)針對(duì)性的語言耸黑,這就是分層思想。換句話說是針對(duì)這個(gè)領(lǐng)域設(shè)計(jì)了一套新類型的DATA篮幢,Data對(duì)外提供的interface就是能力大刊。Data并不存在,Data只是定位三椿,定位是想象的產(chǎn)物缺菌。
所以,之前刻意的遵守web三層架構(gòu)是有問題的搜锰。表現(xiàn)層伴郁,業(yè)務(wù)層,數(shù)據(jù)層蛋叼。
每一層都可以再分層焊傅,而不是將業(yè)務(wù)邏輯都寫在一個(gè)BLOCK中。很多人為了將業(yè)務(wù)挪出來,就又都放到了數(shù)據(jù)層租冠,但是數(shù)據(jù)層不應(yīng)該放置業(yè)務(wù)邏輯鹏倘,這就是概念錯(cuò)位。這也是我為什么不喜歡傳統(tǒng)Web開發(fā)的原因顽爹,太過于教條。沒有彰顯自由意志的氛圍骆姐。程序員們都是想著如何學(xué)會(huì)權(quán)威留下的規(guī)范镜粤。以學(xué)會(huì)的規(guī)范數(shù)量為資本。
4.Why I import the concept—Closure Property?我為什么要提出Closure Property這個(gè)概念玻褪?
— Chapter 2.2 Hierarchical Data and the Closure Property
因?yàn)槿饪剩孌ata成為“水”,Data能夠像水一樣流動(dòng)带射,無限的自由同规。
另外,Data是被Procedure實(shí)現(xiàn)窟社,所以Procedure的Higher-Order特質(zhì)券勺,使得Data具備了Closure Property的屬性。
換句話說灿里,水的屬性不是被添加的关炼,而是Data本來就是水,只不過才被認(rèn)識(shí)到如此匣吊。在此之前儒拂,我們可能認(rèn)為Data是大石頭或者是盒子。
WHAT - 什么是Closure Property色鸳?
例如社痛,自然數(shù)通過加法之后的結(jié)果還是自然數(shù),我們就說自然數(shù)對(duì)于加法是閉合的(Closure Property)
而命雀,自然數(shù)通過減法之后可能是負(fù)數(shù)蒜哀,就不再是自然數(shù),所以自然數(shù)對(duì)于減法就不是閉合的(Closure Property)
注意咏雌,這里不只說自然數(shù)是閉合的(Closure Property)凡怎,而要基于一個(gè)行為:加法。所以赊抖,Closure Property需要Data和Procedure的配合實(shí)現(xiàn)统倒。
所以,本質(zhì)上是在定位Data的流動(dòng)感氛雪,這個(gè)定位的概念就叫做Closure Property房匆,或者說,Lisp中可以通過Porcedure讓Data流動(dòng)起來,我們的編程方向浴鸿,也是要構(gòu)建可以讓Data流動(dòng)起來的那種Procedure井氢。
我們來看例子:Pairs經(jīng)過Cons之后還是Pairs
(cons 1 2) ;這是一個(gè)Pair
(cons (cons 1 2)
(cons 3 4)) ;這還是Pair
所以,我們說Pairs對(duì)于CONS具有Closure Property
The ability to create pairs whose elements are pairs is the essence of list structure’s importance as a representational tool. We refer to this ability as the closure property of cons. In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation.
How - Closure Property的用處是什么岳链?
自由組合出復(fù)雜機(jī)構(gòu)的Data花竞。
Closure is the key to power in any means of combination because it permits us to create hierarchical structures—structures made up of parts, which themselves are made up of parts, and so on.
注意,SICP中說到的Closure并不是函數(shù)式編程中通常說的Closure掸哑,SICP中的Closure是一個(gè)數(shù)學(xué)概念约急。
5.WHY - 我為什么要設(shè)計(jì)List這個(gè)概念出來?
— Chapter 2.2.1 Representing Sequences
因?yàn)槊绶郑黾又虚g層厌蔽,降低復(fù)雜度。這樣遇到可以使用List的業(yè)務(wù)場景摔癣,就不需要每次都從Pairs開始構(gòu)建奴饮。 Pairs —> List —> Business
HOW - 如何設(shè)計(jì)List的interface?
根據(jù)構(gòu)建Data的思想框架framework:Constructors & Selectors
1.設(shè)計(jì)List的Constructors
我們可以利用Pairs的property構(gòu)建復(fù)雜的數(shù)據(jù)結(jié)構(gòu)择浊,例如List:
(cons 1
(cons 2
(cons 3
(cons 4 nil)))
加上一層語法糖戴卜,List可以如此創(chuàng)建:
(list 1 2 3 4)
;等價(jià)于
(cons 1 (cons 2 (cons 3 (cons 4 nil)))
2.設(shè)計(jì)List的Selectors
我們可以直接使用car和cdr
(define one-through-four (list 1 2 3 4))
one-through-four
(1 2 3 4)
(car one-through-four)
1
(cdr one-through-four)
(2 3 4)
3.設(shè)計(jì)更有Power的,具有List特色的interface:List operations:
例如:
- list-ref
(define (list-ref items n)
(if (= n 0)
(car items)
(list-ref (cdr items) (- n 1))))
(define squares (list 1 4 9 16 25))
(list-ref squares 3)
16
- length
(define (length items)
(if (null? items)
0
(+ 1 (length (cdr items)))))
(define odds (list 1 3 5 7))
(length odds)
4
- append
(define (append list1 list2_
(if (null? list1)
list2
(cons (car list1) (append (cdr list1) list2))))
(append squares odds)
(1 4 9 16 25 1 3 5 7)
(append odds squares)
(1 3 5 7 1 3 9 16 25)
- map
Mapping over lists
目的:遍歷list中的每個(gè)元素近她,并且將每個(gè)元素做與變量factor的乘法
傳統(tǒng)思路如下叉瘩,具象思維,頭痛醫(yī)頭:
(define (scale-list items factor)
(if (null? items)
nil
(cons (* (car items) factor) ;這里寫死了業(yè)務(wù)邏輯:乘法
(scale-list (cdr items) factor))))
高級(jí)思路粘捎,抽象思維薇缅,分離通用模式和業(yè)務(wù)接口,利用Higher-Order:
(define (map proc items)
(if (null? items)
nil
(cons (proc (car items));這里的proc就是對(duì)外開放的接口通道
(map proc (cdr items)))))
(map abs (list -10 2.5 -11.6 17)
(10 2.5 11.6 17)
(map (lambda (x) (* x x)) (list 1 2 3 4))
(1 4 9 16)
這就是Map這個(gè)概念的源頭攒磨,Google的Mapping-Reduce思想就是源于此泳桦。
所以說,SICP就是計(jì)算機(jī)世界的元典娩缰。
之前的需求就可以改造成這樣了:
(define (scale-list items factor)
(map (lambda (x) (* x factor))
items))
再一次灸撰,我們體會(huì)到了真正高級(jí)的Abstraction是如何思考問題的。
抽象模式拼坎,但是構(gòu)建內(nèi)部和外部的通道浮毯。給個(gè)性化業(yè)務(wù)以空間。
做到:古典主義(規(guī)范)和浪漫主義(超越)的平衡泰鸡。
忽然醒到债蓝,J的畫就是這種思路,畫的主題很具象(規(guī)范)盛龄,畫的細(xì)很抽象(超越)饰迹。
Map一旦稱為共識(shí)芳誓,Lisp解釋器就可以為Map構(gòu)建特殊,讓Map基于硬件啊鸭,加速運(yùn)行锹淌。所以,規(guī)范都是快的赠制。就像人的習(xí)慣思維不用思考就執(zhí)行了赂摆。
6.有了List,我就可以構(gòu)建更加復(fù)雜的數(shù)據(jù)結(jié)構(gòu)了
— Chapter 2.2.2 Hierarchical Structures
(define x (cons (list 1 2) (list 3 4)))
(lenght x)
3
Lisp中的數(shù)據(jù)結(jié)構(gòu)只用一種钟些,就是List(要我說應(yīng)該是只有Pairs)库正,其他的數(shù)據(jù)結(jié)構(gòu)都是由List組合而來。
為什么厘唾,Lisp要只抽象到List這一層就停止了?
為什么龙誊,Lisp沒有更下沉抚垃,只提供Pairs,而不提供List趟大?
答案是鹤树,這是一種平衡,即逊朽,自由度和可用性的平衡罕伯。
而,Lisp是傾向于自由的元典叽讳,所以只抽象了一層Data追他,即,List岛蚤。
而邑狸,想其他語言對(duì)于數(shù)據(jù)的抽象,就很復(fù)雜了涤妒,因?yàn)樗麄兊亩ㄎ皇歉拥膬A向?qū)嵱眯缘ノ怼8拥目梢宰層脩舴奖愕氖褂幂喿印5秉c(diǎn)就是她紫,如果一直只用這些語言硅堆,會(huì)一直被表象蒙蔽,看不到本質(zhì)的源頭贿讹。
7.現(xiàn)在的程序?qū)懙奶靵y渐逃,如何能讓程序變得有序,簡單易懂围详?
— Chapter 2.2.3 Sequences as Conventional Interfaces
現(xiàn)在的程序的形狀太彎彎繞朴乖。如何能讓程序的形狀簡單成一條線形祖屏。
這就是流程化編程思想的源頭(這也是常江的Proc框架的思想源頭,元典againB蛐摺)
WHY - 程序復(fù)雜混亂的原因是什么袁勺?
因?yàn)椋總€(gè)procedure的input和output都太個(gè)性化畜普。
所以期丰,導(dǎo)致procedure之間的組裝都加入了個(gè)性化的邏輯,這些個(gè)性化的邏輯正式復(fù)雜性的原因吃挑。
HOW - 如何讓Procedure的input和output統(tǒng)一呢钝荡?
啟發(fā),來自于Data概念引出的Closure Property舶衬,如果我們讓Procedure和Data配合起來埠通,都滿足Closure Property,那么逛犹,寫程序端辱,就會(huì)想搭積木/LEGO積木一樣,簡單明了虽画。因?yàn)镻rocedure稱為了統(tǒng)一的鏈接上下游的標(biāo)準(zhǔn)LEGO塊舞蔽。
而之前的構(gòu)建,就像是在用橡皮泥進(jìn)行搭建码撰,每個(gè)鏈接都要花費(fèi)大量的思考渗柿。因?yàn)镻rocedure的鏈接不統(tǒng)一。
這脖岛,正是抽象出Data這個(gè)想象概念的作用朵栖。
想象出一個(gè)不同維度的中間層,讓協(xié)作變得簡單鸡岗。
我們看例子:求一棵樹結(jié)構(gòu)的每個(gè)葉子節(jié)點(diǎn)的平方后的和
1.傳統(tǒng)的思路:以數(shù)據(jù)為中心混槐,圍著數(shù)據(jù)構(gòu)建procedure進(jìn)行操作。這種感覺就像是自己在廚房做菜一樣轩性,圍著食材進(jìn)行操作声登。
(define (sum-odd-squares tree)
(cond
((null? tree) 0)
((not (pair? tree))
(if (odd? tree) (square tree) 0))
(else (+ (sum-odd-squares (car tree))
(sum-odd-squares (cdr tree))))))
2.流程化的思路: 以流程為中心,先構(gòu)建流程體系揣苏,讓Data進(jìn)入流水線悯嗓。這種感覺就像是食品加工工廠。同樣的需求流程化改造后如下:
| emumerate:tree leaves |—>| filter:odd? |—>| map:square |—>| accumulate: +,0 |
(define (sum-odd-squares tree)
(accumulate + 0
(map square
(filter odd?
(enumerate-tree tree)))))
這樣以來卸察,tree作為食材(Data)流經(jīng)了食品加工體系(procedures)
這樣就抽象出了一個(gè)層脯厨,業(yè)務(wù)宏觀層。而之前的方案是沒有邏輯分層的坑质,只有一層合武。分層可以降低復(fù)雜性临梗,對(duì)于Abstraction的理解是不是又加深一步?
讓抽象無處不在吧稼跳!
但也不能無節(jié)制的抽象盟庞,要找到一種平衡。抽象的本質(zhì)就是分類汤善。一類事就是同一個(gè)level的概念什猖。
3.模塊的實(shí)現(xiàn):下面我們將各個(gè)模塊的細(xì)節(jié)實(shí)現(xiàn)吧:這種感覺即是建設(shè)加工體系
這讓我想起一個(gè)故事,一個(gè)村子要引入水源红淡,方案1是用桶拎不狮,方案2是鋪設(shè)水管。
如何選在旱,要根據(jù)業(yè)務(wù)來定摇零,并不能什么需求都大動(dòng)干戈的鋪設(shè)水管。
模塊1:enumerate-tree桶蝎,枚舉tree遂黍,將tree的元素搞成線性list)
(define (enumerate-tree tree)
(cond
((null? tree) nil)
((not (pair? tree)) (list tree))
(else (append (enumerate-tree (car tree))
(enumerate-tree (cdr tree))))))
(enumerate-tree (list 1 (list 2 (list 3 4)) 5))
(1 2 3 4 5)
** 模塊2:filter,按照特定邏輯過濾元素 **
(define (filter predicate sequence)
(cond
((null? sequence) nil)
((predicate (car sequence))
(cons (car sequence)
(filter predicate (cdr sequence))))
(else (filter predicate (cdr sequence)))))
(filter odd? (list 1 2 3 4 5)
(1 3 5)
突然有了一個(gè)領(lǐng)悟俊嗽,要允許自己看不懂代碼,要用直覺去感受代碼的意思铃彰,要想象的去理解绍豁。要虛看,要會(huì)猜牙捉。有點(diǎn)像用英語的感覺竹揍。
模塊3:map,可以復(fù)用之前存在的map邪铲, 放
(define (map proc items)
(if (null? items)
nil
(cons (proc (car items));這里的proc就是對(duì)外開放的接口通道
(map proc (cdr items)))))
(map square (list 1 2 3 4 5))
(1 4 9 16 25)
模塊4:accumulate芬位,聚合,收
(define (accumulate op initial sequence)
(if (null? sequence)
inital
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(accumulate + 0 (list 1 2 3 4 5))
15
(accumulate * 1 (list 1 2 3 4 5))
120
(accumulate cons nil (list 1 2 3 4 5))
(1 2 3 4 5)
這里有個(gè)領(lǐng)悟带到,對(duì)于我自己昧碉,構(gòu)建體系就是養(yǎng)成好習(xí)慣,每一個(gè)模塊就是一個(gè)習(xí)慣揽惹。數(shù)據(jù)就是我們的生命本身被饿。如果想改變命運(yùn),要看的長遠(yuǎn)去構(gòu)建體系(修正習(xí)慣)而不是做眼前的自我評(píng)判搪搏。所以狭握,以后不要評(píng)判自己為什么總是懶惰,為什么總是浪費(fèi)時(shí)間疯溺,而是要耐心的養(yǎng)成好習(xí)慣论颅。
評(píng)判自己然后改正哎垦,就是用水桶拎水,見效快恃疯,但是效益遞減:對(duì)數(shù)曲線漏设。
構(gòu)建新的習(xí)慣體系,就是建立水管系統(tǒng)澡谭,見效慢愿题,但是效益遞增:指數(shù)曲線。
所以蛙奖,工欲善其事潘酗,必先利其器。
看得長遠(yuǎn)些雁仲,做時(shí)間的朋友仔夺。這也是我為什么投資自己重構(gòu)底層概念系統(tǒng)的思路。
另一個(gè)啟發(fā)攒砖,要讓自己的生活流程簡單化缸兔。這樣就不用總想該干啥。一段時(shí)間就干一個(gè)事吹艇,要干的事的類別要少惰蜜,要抽象。
例如啃SICP的流程:
Data:每個(gè)章節(jié)
體系:Read—>Write—>Exercise
7.Example:A Picture Language
TODO