讀《代碼大全2》,已經(jīng)讀了一半,喘口氣。總結(jié)八個(gè)字:百科全書(shū),受益匪淺。小到一個(gè)賦值語(yǔ)句、一個(gè)循環(huán)的編寫(xiě),大到需求分析、架構(gòu)設(shè)計(jì),無(wú)所不包,看后半部分目錄,更是扯到了重構(gòu)、軟件工藝、程序員的性格特征這樣的話題。恰好手邊的工作暫時(shí)比較有閑,可以實(shí)踐下“創(chuàng)建高質(zhì)量的代碼”中的部分建議,晚上讀書(shū),第二天就重構(gòu),樂(lè)在其中。這一部分中對(duì)設(shè)計(jì)、子程序、類、變量、語(yǔ)句的處理建議,可能你平常已經(jīng)在這么做,可作者這么精辟地概括出來(lái)讓人嘆服,而有些地方是你平常絕對(duì)很少注意的,特別是在變量和三種常見(jiàn)控制語(yǔ)句的處理上。
說(shuō)說(shuō)我認(rèn)為是缺點(diǎn)的地方,就是作者貌似對(duì)函數(shù)式語(yǔ)言了解很少,舉的例子全部用的是廣泛應(yīng)用的靜態(tài)語(yǔ)言(c/c++,java,vb)。例如作者有這么一句話:
如果為我工作的程序員用遞歸去計(jì)算階乘,那么我寧愿換人。作者對(duì)遞歸的態(tài)度相當(dāng)謹(jǐn)慎,這在靜態(tài)命令式語(yǔ)言中顯然是正確的,但是在函數(shù)式語(yǔ)言中,由于有尾遞歸優(yōu)化的存在,遞歸反而是最自然的形式,況且我打心里認(rèn)為遞歸更符合人類思維。請(qǐng)注意,在FP中只有尾遞歸的程序才是線性迭代的,否則寫(xiě)出來(lái)的遞歸可能是線性遞歸或者樹(shù)形遞歸,兩種情況下都可能導(dǎo)致堆棧溢出并且性能較差。
scheme寫(xiě)階乘:
(define (fac n)
(if (= 1 n)
1
(* n (fac (- n 1)))))
顯然這個(gè)版本不是尾遞歸,計(jì)算過(guò)程是一個(gè)線性遞歸過(guò)程,計(jì)算(fac 4)的過(guò)程如下:
(* 4 (fac 3))
(* 4 (3 * (fac 2)))
(* 4 (3 * (* 2 (fac 1))))
(* 4 (3 * (* 2 1)))
(* 4 (3 * 2))
(* 4 6)
24
因?yàn)榻忉屍魇遣捎脩?yīng)用序求值,需要將表達(dá)式完全展開(kāi),然后依次求值,在這個(gè)過(guò)程中,解釋器內(nèi)部需要保存一條長(zhǎng)長(zhǎng)的推遲計(jì)算的軌跡。
改寫(xiě)成一個(gè)尾遞歸版本:
(define (fac n)
(define (fac-iter product n)
(if (= 1 n)
product
(fac-iter (* n product) (- n 1))))
(fac-iter 1 n))
我們來(lái)看看它的計(jì)算過(guò)程:
(fac-iter 1 4)
(fac-iter 4 3)
(fac-iter 12 2)
(fac-iter 24 1)
24
可以看到,在這個(gè)過(guò)程中,解釋器不需要保存計(jì)算軌跡,迭代的中間結(jié)果通過(guò)product變量來(lái)保存,這是一個(gè)線性迭代的計(jì)算過(guò)程。
最后再看一個(gè)
斐波拉契數(shù)列的例子:
(define (fib n)
(cond ((= n 0) 0)
((= n 1) 1)
(else
(+ (fib (- n 1)) (fib (- n 2))))))
這個(gè)計(jì)算過(guò)程展開(kāi)是一個(gè)樹(shù)形遞歸的過(guò)程(為什么說(shuō)是樹(shù)形?展開(kāi)下計(jì)算過(guò)程就知道),改寫(xiě)為線性迭代:
(define (fib n)
(define (fib-iter a b count)
(if (= count 0)
b
(fib-iter (+ a b) a (- count 1))))
(fib-iter 1 0 n))
上述的內(nèi)容在sicp第一章里有更詳細(xì)的介紹和討論。