因?yàn)榍岸螘r(shí)間研究了很久的圖形學(xué)灸促,也寫(xiě)了很多向量和矩陣的運(yùn)算函數(shù),但是其中一些程序的編寫(xiě)和設(shè)計(jì)難度無(wú)疑讓我很難受后控,由于之前看了alan kay寫(xiě)的關(guān)于smalltalk80的歷史侣背,我突然意識(shí)到使用lambda來(lái)計(jì)算矩陣,或許會(huì)有更簡(jiǎn)單的方法堤瘤,相比于alan kay的一切都是對(duì)象的思想,它達(dá)到了無(wú)限的伸縮性浆熔,對(duì)數(shù)字和數(shù)據(jù)結(jié)構(gòu)都達(dá)到了很好的建模本辐,但是奇怪的一點(diǎn)是,使用面向?qū)ο笳Z(yǔ)言解決基礎(chǔ)算法,好像并沒(méi)有實(shí)質(zhì)性的進(jìn)步慎皱,我們先來(lái)看一個(gè)c程序:
#include <stdio.h>
#include <string.h>
int bank(char * msg, int val){
static balance = 0;
if(strcmp(msg,"set") == 0)
balance = val;
else if(strcmp(msg,"do") == 0)
balance += val;
else if(strcmp(msg,"get") == 0)
return balance;
}
int main(){
bank("set",10);
bank("do",10);
printf("%d",bank("get",0));
return 0;
}
這個(gè)c程序封裝了一個(gè)static變量balance老虫,外部環(huán)境可以通過(guò) char * msg 來(lái)訪問(wèn)它,但是我們的程序還是基于變量這一個(gè)前提,無(wú)論是在smalltalk內(nèi)部茫多,還是java祈匙,都保留了內(nèi)部狀態(tài),這樣的程序?qū)嶋H上只帶來(lái)了非破壞性地梨,也就是把程序使用通信的方式組織在一起菊卷,拿到消息的每個(gè)部分只需要對(duì)接口編程缔恳,而不需要對(duì)實(shí)現(xiàn)編程宝剖,提高了程序的利用率,降低了總體代碼變化的部分歉甚,從而降低了實(shí)現(xiàn)功能的編寫(xiě)難度万细,并沒(méi)有真正從算法的角度去改變程序的內(nèi)部結(jié)構(gòu),在面對(duì)一個(gè)復(fù)雜的大型程序帶來(lái)的復(fù)雜性纸泄,這樣的方法論是很好的赖钞,但是實(shí)際上,我們還可以更進(jìn)一步思考聘裁,我們的程序?qū)嶋H上并不是在通信雪营,取而代之的是,它們是在計(jì)算衡便。
有人說(shuō)献起,這不是廢話嗎?但是仔細(xì)想想镣陕,我們的用戶使用某個(gè)軟件谴餐,到底是在干嘛,他們是在通信嗎呆抑,還是說(shuō)他們?cè)谟?jì)算岂嗓?其實(shí)可以推而廣之,我們?nèi)祟惈@取知識(shí)鹊碍,改造現(xiàn)實(shí)世界厌殉,計(jì)算更普遍,還是通信更普遍侈咕,可能不同的人有不同的結(jié)論公罕,但是兩者在某些時(shí)候是等同的,比如說(shuō)乎完,一個(gè)正在建造航天飛機(jī)的工程師熏兄,他急需解決一個(gè)空氣動(dòng)力學(xué)方程從而設(shè)計(jì)機(jī)翼的寬度,他有可能會(huì)選擇自己計(jì)算出來(lái),他也可以找某個(gè)團(tuán)隊(duì)里的數(shù)學(xué)很好的助手摩桶,計(jì)算出來(lái)桥状,他只要知道結(jié)果就好了,然而這個(gè)助手的可信度硝清,或許比起計(jì)算機(jī)辅斟,會(huì)有一定的差異,他有可能更相信某個(gè)科學(xué)計(jì)算軟件芦拿,也可能正好相反士飒,但是可以知道的是,他自己計(jì)算出來(lái)蔗崎,和借助某些外部力量知道了這個(gè)方程的解酵幕,只取決于他的信任度,答案只是一個(gè)比特位描述的符號(hào)或者方程式缓苛,他只需要關(guān)注它的可信度芳撒,到底是計(jì)算得來(lái),還是某種通信得來(lái)未桥,兩者沒(méi)有本質(zhì)上的區(qū)別笔刹,他只是描述出了問(wèn)題,然后想知道答案冬耿,只要答案的可信程度夠高舌菜,他就達(dá)到了自己的目的,半個(gè)月之后亦镶,機(jī)翼制作完成日月,他可以做實(shí)機(jī)試飛實(shí)驗(yàn)了,他早已經(jīng)忘記那個(gè)微不足道的微分方程染乌,但是某一天山孔,他可能又要重新?lián)炱饋?lái),他可能已經(jīng)記錄在了某個(gè)記事本上荷憋,但是他已經(jīng)忘記是哪個(gè)本子了台颠,但是他有一個(gè)好習(xí)慣,就是給自己的筆記本寫(xiě)時(shí)間勒庄,只要翻閱某段時(shí)間內(nèi)的筆記串前,他肯定可以找到它,我們發(fā)現(xiàn)了某個(gè)找回信息的普遍的方法論实蔽,我們通過(guò)給信息添加標(biāo)簽荡碾,它只要是某個(gè)類型的信息,可以有不同的查詢方式局装,這樣就可以把固定查找的過(guò)程坛吁,轉(zhuǎn)化為模糊查詢的過(guò)程劳殖,我們只是想要答案,并且定位它拨脉,給定了一個(gè)范圍以后哆姻,并不需要準(zhǔn)確地記住它的位置,需要的時(shí)候再去查詢玫膀,我們的大腦便可以降低思考的難度矛缨,我們?cè)賮?lái)看范圍到底是什么,最初范圍這個(gè)詞和類型是聯(lián)系到一起的帖旨,一個(gè)類型的概念箕昭,它包含了無(wú)數(shù)它的實(shí)例,這些實(shí)例都是它涵蓋的范圍解阅,我們只需要知道這個(gè)范圍落竹,它映射到更大更明確的內(nèi)涵,該微分方程屬于某個(gè)航天飛機(jī)項(xiàng)目瓮钥,這個(gè)項(xiàng)目屬于某段時(shí)間筋量,記事本都是這個(gè)時(shí)間的,所以我們肯定可以在那里找到它碉熄。
接下來(lái)是我們的另一個(gè)c語(yǔ)言程序,它表示了一個(gè)矩陣計(jì)算
void mul_matrix(int M,int N, int K,int**A,int**B,int**C){
for (i = 0; i < M; i++){
for (j = 0; j < N; j++){
int sum = 0;
for (m = 0; m < K; m++)
sum = sum + A[i][m] * B[m][j];
C[i][j] = sum;
}
}
}
看起來(lái)這個(gè)程序好像很簡(jiǎn)單肋拔,實(shí)際上它的編寫(xiě)隱含的都是機(jī)器的思維锈津,它理解起來(lái)是非常深?yuàn)W的,以至于很多人會(huì)看著它深思很久凉蜂,因?yàn)樗话魏蔚某渥愕母拍顑?nèi)涵琼梆,它就是數(shù)組,和矩陣扯不上任何關(guān)系窿吩,甚至和向量也沒(méi)有任何關(guān)系茎杂,它是如此晦澀和陌生,循環(huán)纫雁,加減乘除煌往,控制條件,這些原始的部分死死地連接到了一起轧邪,無(wú)法使用更好的辦法分開(kāi)它們刽脖,并且每一次的計(jì)算都寫(xiě)死在了算法給定的數(shù)組里,所以說(shuō)用c語(yǔ)言忌愚,甚至c++和java等編程曲管,它們是等價(jià)的,算法的部分無(wú)法用更好的方式分解硕糊,它們死死地耦合在一起院水,一個(gè)問(wèn)題無(wú)法被分解為某些相同的計(jì)算部分腊徙,從而讓每一次的思考都重復(fù)出現(xiàn),人的大腦提前計(jì)算了一次檬某,然后再進(jìn)入了計(jì)算機(jī)運(yùn)算昧穿。
實(shí)際上人腦思考問(wèn)題并不是這樣的方式,人腦擅長(zhǎng)模糊思考橙喘,通過(guò)一個(gè)概念來(lái)引用它的范圍时鸵,從而確定它包含的內(nèi)容,每一個(gè)計(jì)算都可以用足夠的信息來(lái)描述它的運(yùn)作方式厅瞎,把計(jì)算過(guò)程設(shè)計(jì)為通信饰潜,而不是計(jì)算,要獲取數(shù)據(jù)和簸,則向函數(shù)發(fā)送一條信息彭雾,它返回一個(gè)你需要的信息就是數(shù)據(jù),lambda演算很好的做到了這一點(diǎn)锁保,它并不執(zhí)著于把每一條數(shù)據(jù)都提前計(jì)算好薯酝,而是用到的時(shí)候再拿出來(lái),由于具有足夠多的描述信息來(lái)確定它的范圍爽柒,所以確定內(nèi)容的時(shí)候再計(jì)算吴菠,可以避免提前把每一個(gè)數(shù)據(jù)都演算一遍,于是大大降低了思考的難度浩村,這是用scheme實(shí)現(xiàn)同樣的c代碼的功能
(define (mul-vec v1 v2)
(if (= (length v1) (length v2))
(if (null? v1) 0
(+ (* (car v1) (car v2))
(mul-vec (cdr v1) (cdr v2))))
'error))
(define (make-matrix row col data)
(lambda msg
(if (= (length msg) 1)
(cond
((eq? (car msg) 'row) row)
((eq? (car msg) 'col) col))
(list-ref data (+ (* (car msg) col) (cadr msg))))))
(define (col-ref matrix col-index)
(define (col-loop matrix row-index)
(if (= row-index (matrix 'row))
'()
(cons (matrix row-index col-index) (col-loop matrix (+ row-index 1)))))
(col-loop matrix 0))
(define (row-ref matrix row-index)
(define (row-loop matrix col-index)
(if (= col-index (matrix 'col))
'()
(cons (matrix row-index col-index) (row-loop matrix (+ col-index 1)))))
(row-loop matrix 0))
(define (mul-matrix A B)
(if (= (A 'col) (B 'row))
(lambda msg
(if (= (length msg) 1)
(cond
((eq? (car msg) 'row) (A 'row))
((eq? (car msg) 'col) (B 'col)))
(mul-vec (row-ref A (car msg)) (col-ref B (cadr msg)))))
(lambda msg 'error)))
代碼好像長(zhǎng)了很多做葵,但是實(shí)際上上面的代碼可以用語(yǔ)法糖的形式實(shí)現(xiàn)
(m ([mx i j _] * [mx j k _]) n) = (m [mx i j _]) * ([mx j k _] n)
主要的mul-matrix函數(shù)只做了一個(gè)事情,把A的行和B的列進(jìn)行向量相乘的函數(shù)作為一個(gè)計(jì)算結(jié)果心墅,每一次使用該函數(shù)酿矢,就是獲取它的元素,由于之前我們把list等等都實(shí)現(xiàn)為lambda函數(shù)怎燥,所以相乘之后的矩陣函數(shù)實(shí)際上性質(zhì)上等同于make構(gòu)造的函數(shù)瘫筐,整個(gè)系統(tǒng)無(wú)縫地揉合在了一起,然而我們只是描述了某些規(guī)則铐姚,它并沒(méi)有把所有的元素都計(jì)算好策肝,我們并不需要都去計(jì)算好,我們只是把A和B給了它谦屑,它自己知道怎么算出來(lái)驳糯,它描述了將要執(zhí)行的計(jì)算,這里沒(méi)有可怕的循環(huán)和大量的遞歸氢橙,只有足夠多的描述酝枢,它精確地描述了,矩陣的行和列到底是什么悍手,向量的乘法到底是啥帘睦,矩陣的定義到底是什么袍患,矩陣乘法的定義到底是什么,它就可以計(jì)算了竣付,它是如此地優(yōu)雅诡延,以至于讓人想到了萬(wàn)物的運(yùn)行狀態(tài),到底是一系列的狀態(tài)變化古胆,還是即將執(zhí)行的計(jì)算對(duì)象肆良,每一次計(jì)算都帶來(lái)了新的數(shù)據(jù),假如不計(jì)算逸绎,你無(wú)法確定它到底是什么狀態(tài)惹恃,每一次計(jì)算,你就可以知道它內(nèi)部到底有什么棺牧。