上一章展示了續(xù)延是如何使運(yùn)行中的程序獲知自己的狀態(tài),并且把它保存起來(lái)以便之后重新執(zhí)行的。這一章將討論一種計(jì)算模型,在這種模型中,計(jì)算機(jī)運(yùn)行的不是單個(gè)程序,而是一組獨(dú)立的進(jìn)程。進(jìn)程的概念和程序狀態(tài)這一概念相當(dāng)接近。通過(guò)在前一章的宏的基礎(chǔ)上再寫一層宏,我們就可以把多進(jìn)程的機(jī)制融入到 Common Lisp 程序中。
多進(jìn)程這種表現(xiàn)形式,可以很方便地表示并行處理多個(gè)任務(wù)的程序。傳統(tǒng)的處理器同時(shí)只能執(zhí)行一條指令。我們稱多進(jìn)程能同時(shí)處理多件事情,并不是說(shuō)它通過(guò)某種方式克服了硬件的限制,它真正的含義是:它使得我們可以在一個(gè)新的抽象層面上進(jìn)行思考,在這個(gè)層面上我們不需要明確地指定計(jì)算機(jī)在任何給定的時(shí)間在做什么。就像虛擬內(nèi)存給我們制造了一個(gè)錯(cuò)覺(jué),似乎計(jì)算機(jī)的可用內(nèi)存比它的物理內(nèi)存還要大,同樣的道理,多進(jìn)程的概念使得我們可以假設(shè)計(jì)算機(jī)可以一次運(yùn)行多個(gè)程序。
從傳統(tǒng)上說(shuō),對(duì)進(jìn)程的研究屬于操作系統(tǒng)領(lǐng)域的范疇。但進(jìn)程抽象帶來(lái)的進(jìn)步并不局限于操作系統(tǒng)。它們?cè)谄渌麑?shí)時(shí)的應(yīng)用程序和計(jì)算機(jī)仿真中一樣能大展身手。
有很多對(duì)于多進(jìn)程的研究,它們的目的都是為了避免出現(xiàn)某些特定類型的問(wèn)題。死鎖是多進(jìn)程的一個(gè)經(jīng)典問(wèn)題:兩個(gè)進(jìn)程同時(shí)停下等待另一個(gè)做某些事情,就像兩個(gè)人都拒絕在另一個(gè)人之前跨過(guò)門檻。另一個(gè)問(wèn)題是查詢有可能碰到系統(tǒng)中數(shù)據(jù)不一致的狀態(tài) 例如,一個(gè)余額查詢正好在系統(tǒng)將資金從一個(gè)賬戶轉(zhuǎn)移到另一個(gè)賬戶時(shí)發(fā)生。這一章只討論進(jìn)程抽象本身;這里展示的代碼可以用來(lái)測(cè)試避免死鎖和不一致?tīng)顟B(tài)的算法,但代碼本身沒(méi)有對(duì)這些問(wèn)題提供任何保護(hù)。
這一章中的實(shí)現(xiàn)遵循了本書所有程序默默恪守的一條準(zhǔn)則:盡可能少的擾亂Lisp。在本質(zhì)上來(lái)說(shuō),程序應(yīng)該盡可能多的讓自己像是對(duì)語(yǔ)言的修改,而不是用語(yǔ)言寫就的一個(gè)獨(dú)立的應(yīng)用程序。使程序與Lisp 協(xié)調(diào)一致可以使得程序更為健壯,好比部件配合良好的機(jī)器。這樣做也能事半功倍;有時(shí)你可以讓Lisp 為你代勞數(shù)量驚人的工作。
這一章的目標(biāo)是構(gòu)建一個(gè)支持多進(jìn)程的的語(yǔ)言。我們的策略是通過(guò)添加一些操作符,將Lisp 變成這樣的語(yǔ)言。我們語(yǔ)言的基本構(gòu)成元素如下:
函數(shù)?由前一章的 =defun 或者 =lambda 宏定義。
進(jìn)程?由函數(shù)調(diào)用實(shí)例化?;顒?dòng)進(jìn)程的數(shù)量和一個(gè)函數(shù)能夠?qū)嵗倪M(jìn)程數(shù)量都沒(méi)有限制。每個(gè)進(jìn)程有一個(gè)優(yōu)先級(jí),初始值由創(chuàng)建時(shí)給出的參數(shù)指定。
等待表達(dá)式(Waitexpressions)?等待表達(dá)式接受一個(gè)變量,一個(gè)測(cè)試表達(dá)式和一段代碼體。如果進(jìn)程遇到等待表達(dá)式,進(jìn)程將在這一點(diǎn)被掛起,直到測(cè)試表達(dá)式返回真。一旦進(jìn)程重新開(kāi)始執(zhí)行,代碼體會(huì)被求值,變量則被綁定到測(cè)試表達(dá)式的值。測(cè)試表達(dá)式通常不應(yīng)該有副作用,因?yàn)樗磺笾档臅r(shí)間和頻率沒(méi)有任何保證。
調(diào)度?通過(guò)優(yōu)先級(jí)來(lái)完成。在所有能夠重新開(kāi)始執(zhí)行的進(jìn)程中,系統(tǒng)會(huì)運(yùn)行優(yōu)先級(jí)最高的進(jìn)程。
默認(rèn)進(jìn)程?在其他進(jìn)程都不能執(zhí)行時(shí)運(yùn)行。它是一個(gè) read-eval-print 循環(huán)。
創(chuàng)建和刪除?絕大多數(shù)對(duì)象的操作可以即時(shí)進(jìn)行。正在運(yùn)行中的進(jìn)程可以定義新的函數(shù),實(shí)例化或者殺死進(jìn)程。
續(xù)延使得保存 Lisp 程序的狀態(tài)成為可能。能夠同時(shí)保存多個(gè)狀態(tài)離實(shí)現(xiàn)多進(jìn)程也不太遠(yuǎn)了。有了前一章定義的宏做基礎(chǔ),我們只要不到 60 行的代碼就可以實(shí)現(xiàn)多進(jìn)程。
[示例代碼 21.1] 進(jìn)程結(jié)構(gòu)及實(shí)例化
(defstruct proc pri state wait)
(proclaim '(special *procs* *proc*))
(defvar *halt* (gensym))
(defvar *default-proc*
(make-proc :state #'(lambda (x)
(format t "~%>> ")
(princ (eval (read)))
(pick-process))))
(defmacro fork (expr pri)
'(prog1 ',expr
(push (make-proc
:state #'(lambda (,(gensym))
,expr
(pick-process))
:pri ,pri)
*procs*)))
(defmacro program (name args &body body)
'(=defun ,name ,args
(setq *procs* nil)
,@body
(catch *halt* (loop (pick-process)))))
[示例代碼 21.1] 和圖 21.2 包含了所有用來(lái)支持多進(jìn)程的代碼。[示例代碼 21.1] 包含了基本數(shù)據(jù)結(jié)構(gòu)、默認(rèn)進(jìn)程、初始化、進(jìn)程實(shí)例化的代碼。進(jìn)程,或者說(shuō)procs,具有如下結(jié)構(gòu):
pri : 進(jìn)程的優(yōu)先級(jí),它應(yīng)該是一個(gè)正數(shù)。
state : 是一個(gè)續(xù)延,它用來(lái)表示一個(gè)掛起進(jìn)程的狀態(tài)。我們可以 funcall 一個(gè)進(jìn)程的 state 來(lái)重新啟動(dòng)它。
wait : 通常是一個(gè)函數(shù),如果要讓進(jìn)程重新執(zhí)行,它必須返回真,但剛創(chuàng)建的進(jìn)程的 wait 為 nil 。wait 為空的進(jìn)程總是可以被重新執(zhí)行。
程序使用三個(gè)全局變量:procs ,當(dāng)前被掛起的進(jìn)程列表;proc ,正在運(yùn)行的進(jìn)程;還有 default-proc ,默認(rèn)進(jìn)程。
默認(rèn)進(jìn)程僅當(dāng)沒(méi)有其他進(jìn)程可以運(yùn)行時(shí)才會(huì)運(yùn)行。它模擬 Lisp 的 toplevel 循環(huán)。在這個(gè)循環(huán)中,用戶可以終止程序,或者輸入讓掛起進(jìn)程恢復(fù)執(zhí)行的表達(dá)式。請(qǐng)注意,默認(rèn)進(jìn)程顯式地調(diào)用了 eval。這是少數(shù)幾個(gè)合理使用 eval 的情形之一。一般來(lái)說(shuō),我們不贊成在運(yùn)行時(shí)調(diào)用 eval ,這有兩個(gè)原因:
效率低下:eval 直接處理原始列表,要么當(dāng)場(chǎng)進(jìn)行編譯,要么在解釋器中進(jìn)行求值。不管哪種方式都比先編譯再調(diào)用來(lái)得慢。
通常來(lái)說(shuō),顯式調(diào)用eval 就像在機(jī)場(chǎng)禮品店買東西一樣。已經(jīng)是最后關(guān)頭,你只得高價(jià)購(gòu)買選擇有限的劣質(zhì)商品。
像本例這樣兩條理由都不適用的情況是很少見(jiàn)的。我們沒(méi)法提前將表達(dá)式編譯好。直到讀取它們的時(shí)候才知道表達(dá)式是什么,所以沒(méi)法事先知道。同樣的,表達(dá)式無(wú)法引用它周遭的詞法環(huán)境,因?yàn)樵趖oplevel 輸入的表達(dá)式處于空的詞法環(huán)境中。事實(shí)上,這個(gè)函數(shù)的定義直接反映了它的英語(yǔ)描述:它讀取并求值用戶的輸入。
宏 fork 使用一個(gè)函數(shù)調(diào)用來(lái)實(shí)例化進(jìn)程。函數(shù)像平時(shí)一樣由 =defun 定義:
(=defun foo (x)
(format t "Foo was called with ~A.~%" x)
(=values (1+ x)))
現(xiàn)在當(dāng)我們以一個(gè)函數(shù)調(diào)用和優(yōu)先級(jí)數(shù)值作為參數(shù)調(diào)用 fork 時(shí):
(fork (foo 2) 25)
一個(gè)新進(jìn)程被加入到了 procs 里面。新進(jìn)程的優(yōu)先級(jí)為 25,因?yàn)樗€沒(méi)有執(zhí)行,所以 proc-wait 為 nil ,而 proc-state 包含了以 2 為參數(shù)的對(duì) foo 的調(diào)用。
宏 program 使我們可以創(chuàng)建一組進(jìn)程并一起執(zhí)行它們。下面的定義:
(program two-foos (a b)
(fork (foo a) 99)
(fork (foo b) 99))
宏展開(kāi)成了兩個(gè) fork 表達(dá)式,被夾在負(fù)責(zé)清除掛起進(jìn)程的代碼,以及不斷選擇進(jìn)程來(lái)運(yùn)行的代碼中間。在這個(gè)循環(huán)外面,program 宏設(shè)置了一個(gè) tag,把控制流拋(throw) 到這個(gè) tag 的話,就會(huì)終止這個(gè)程序(program)。因?yàn)檫@個(gè) tag 是個(gè)生成符號(hào),所以它不會(huì)與用戶設(shè)置的 tag 沖突。定義成 program 的一組進(jìn)程不返回任何值,而且它們只應(yīng)該在 toplevel 被調(diào)用。
進(jìn)程實(shí)例化之后,進(jìn)程調(diào)度代碼開(kāi)始執(zhí)行。它的代碼見(jiàn) [示例代碼 21.2]。函數(shù) pick-process 在可以繼續(xù)執(zhí)行的進(jìn)程中,選出優(yōu)先級(jí)最高的一個(gè),然后運(yùn)行它。把這個(gè)進(jìn)程找出來(lái)是 most-urgent-process 的工作。如果一個(gè)掛起的進(jìn)程沒(méi)有 wait 函數(shù)或者它的 wait 函數(shù)返回真,那么它就被允許運(yùn)行。在所有被允許運(yùn)行的進(jìn)程中,具有最高優(yōu)先級(jí)的被選中。勝出的進(jìn)程和它的 wait 函數(shù)(如果有的話) 返回的值被返回給 pick-process 。獲勝進(jìn)程總是存在,因?yàn)槟J(rèn)進(jìn)程總是想要執(zhí)行。
[示例代碼 21.2] 中其余的代碼定義了用于在進(jìn)程間切換控制權(quán)的操作符。標(biāo)準(zhǔn)的等待表達(dá)式是wait ,就像[示例代碼 21.3] 中函數(shù) pedestrian 使用的那樣。在這個(gè)例子中,進(jìn)程一直等到列表 open-doors 中有東西為止,然后打印一條消息:
> (ped)
>> (push 'door2 *open-doors*)
Entering DOOR2
>> (halt)
NIL
一個(gè) wait 在實(shí)質(zhì)上來(lái)說(shuō)與 =bind (第 20.2 節(jié)) 相似,而且有著一樣的限制,那就是它必須在最后被求值。任何我們希望在wait 之后執(zhí)行的東西必須被放在它的代碼體中。因此如果我們想要讓一個(gè)進(jìn)程等待多次,那等待表達(dá)式必須被嵌套。通過(guò)聲明互相針對(duì)的事實(shí),進(jìn)程可以相互配合以達(dá)到某個(gè)目標(biāo),就像在 [示例代碼 21.4] 中一樣。
【】譯者注:即 (eval (read)) 【】譯者注:catch 操作符的用法可見(jiàn)CLHS 中的Special Operator CATCH 一節(jié)。
[示例代碼 21.2] 進(jìn)程調(diào)度
(defun pick-process ()
(multiple-value-bind (p val) (most-urgent-process)
(setq *proc* p
*procs* (delete p *procs*))
(funcall (proc-state p) val)))
(defun most-urgent-process ()
(let ((proc1 *default-proc*) (max -1) (val1 t))
(dolist (p *procs*)
(let ((pri (proc-pri p)))
(if (> pri max)
(let ((val (or (not (proc-wait p))
(funcall (proc-wait p)))))
(when val
(setq proc1 p
max pri
val1 val))))))
(values proc1 val1)))
(defun arbitrator (test cont)
(setf (proc-state *proc*) cont
(proc-wait *proc*) test)
(push *proc* *procs*)
(pick-process))
(defmacro wait (parm test &body body)
'(arbitrator #'(lambda () ,test)
#'(lambda (,parm) ,@body)))
(defmacro yield (&body body)
'(arbitrator nil #'(lambda (,(gensym)) ,@body)))
(defun setpri (n) (setf (proc-pri *proc*) n))
(defun halt (&optional val) (throw *halt* val))
(defun kill (&optional obj &rest args)
(if obj
(setq *procs* (apply #'delete obj *procs* args))
(pick-process)))
[示例代碼 21.3] 有一個(gè)等待的進(jìn)程
(defvar *open-doors* nil)
(=defun pedestrian ()
(wait d (car *open-doors*)
(format t "Entering ~A~%" d)))
(program ped ()
(fork (pedestrian) 1))
如果被給予相同的 door ,從 visitor 和 host 實(shí)例化的進(jìn)程會(huì)通過(guò)黑板上的消息互相交換控制權(quán):
> (ballet)
[示例代碼 21.4]: 利用黑板進(jìn)行同步
(defvar *bboard* nil)
(defun claim (&rest f) (push f *bboard*))
(defun unclaim (&rest f) (pull f *bboard* :test #'equal))
(defun check (&rest f) (find f *bboard* :test #'equal))
(=defun visitor (door)
(format t "Approach ~A. " door)
(claim 'knock door)
(wait d (check 'open door)
(format t "Enter ~A. " door)
(unclaim 'knock door)
(claim 'inside door)))
(=defun host (door)
(wait k (check 'knock door)
(format t "Open ~A. " door)
(claim 'open door)
(wait g (check 'inside door)
(format t "Close ~A.~%" door)
(unclaim 'open door))))
(program ballet ()
(fork (visitor 'door1) 1)
(fork (host 'door1) 1)
(fork (visitor 'door2) 1)
(fork (host 'door2) 1))
Approach DOOR2. Open DOOR2. Enter DOOR2. Close DOOR2.
Approach DOOR1. Open DOOR1. Enter DOOR1. Close DOOR1.
>>
還有另外一類更簡(jiǎn)單的等待表達(dá)式:yield ,它的唯一目的是讓其他更高優(yōu)先級(jí)的進(jìn)程有機(jī)會(huì)運(yùn)行。
setpri 重置當(dāng)前進(jìn)程的優(yōu)先級(jí),一個(gè)進(jìn)程可能在執(zhí)行 setpri 表達(dá)式后想要讓出控制權(quán)。就像 wait 一樣,在 yield 之后執(zhí)行的代碼都必須被放在它的代碼體中。
[示例代碼 21.5] 中的程序說(shuō)明了這兩個(gè)操作符如何相互工作。開(kāi)始時(shí),野蠻人有兩個(gè)目的:占領(lǐng)羅馬和掠奪它。占領(lǐng)城市有著(稍微) 高一些的優(yōu)先級(jí),因此會(huì)先執(zhí)行。然而,在城市淪陷之后,capture 進(jìn)程的優(yōu)先級(jí)減小到 1 。之后會(huì)有一次投票,而 plunder ,作為最高優(yōu)先級(jí)的進(jìn)程開(kāi)始運(yùn)行。
> (barbarians)
Liberating ROME.
Nationalizing ROME.
Refinancing ROME.
Rebuilding ROME.
>>
只有在蠻族掠奪了羅馬的宮殿,并勒索了貴族之后,capture 進(jìn)程才會(huì)恢復(fù)執(zhí)行,此時(shí)他們開(kāi)始為其領(lǐng)地建筑防御工事。
等待表達(dá)式的背后是一個(gè)更通用的 arbitrator。這個(gè)函數(shù)保存當(dāng)前進(jìn)程,然后調(diào)用 pick-process 來(lái)再次執(zhí)行某個(gè)進(jìn)程(有可能與當(dāng)前進(jìn)程為同一個(gè))。它有兩個(gè)參數(shù):一個(gè)測(cè)試函數(shù)和一個(gè)續(xù)延。前者會(huì)被存儲(chǔ)為掛起進(jìn)程的 proc-wait ,在以后被調(diào)用來(lái)檢查它是否可以被重新執(zhí)行。
宏 wait 和 yield 通過(guò)簡(jiǎn)單的把它們的代碼體包在 --表達(dá)式中來(lái)建立這個(gè)續(xù)延函數(shù)。例如:
[示例代碼 21.5] 改變進(jìn)程優(yōu)先級(jí)的效果
(=defun capture (city)
(take city)
(setpri 1)
(yield
(fortify city)))
(=defun plunder (city)
(loot city)
(ransom city))
(defun take (c) (format t "Liberating ~A.~%" c))
(defun fortify (c) (format t "Rebuilding ~A.~%" c))
(defun loot (c) (format t "Nationalizing ~A.~%" c))
(defun ransom (c) (format t "Refinancing ~A.~%" c))
(program barbarians ()
(fork (capture 'rome) 100)
(fork (plunder 'rome) 98))
(wait d (car *bboard*) (=values d))
被展開(kāi)成:
(arbitrator #'(lambda () (car *bboard*))
#'(lambda (d) (=values d)))
如果代碼遵循了 [示例代碼 20.5] 列出的限制,構(gòu)造一個(gè) wait 代碼體的閉包就可以保存當(dāng)前的整個(gè)續(xù)延。隨著它的 =values 被展開(kāi),第二個(gè)參數(shù)變成:
#'(lambda (d) (funcall *cont* d))
由于這個(gè)閉包中有一個(gè)指向 cont 的引用,被這個(gè)等待函數(shù)掛起的進(jìn)程將會(huì)擁有一個(gè)句柄(handle),通過(guò)它,這個(gè)進(jìn)程就能回到它當(dāng)初被掛起的那一刻。
halt 操作符通過(guò)將控制權(quán)拋回program 展開(kāi)式建立的標(biāo)簽終止整個(gè)進(jìn)程組。它接受一個(gè)可選參數(shù),該參數(shù)的值會(huì)被作為這個(gè)進(jìn)程組的值返回。因?yàn)槟J(rèn)進(jìn)程始終想要執(zhí)行,所以終止整個(gè)程序的唯一的方法是顯式的調(diào)用halt 。halt 后面是什么代碼并沒(méi)有關(guān)系,因?yàn)檫@些代碼不會(huì)被求值。
單個(gè)進(jìn)程可以通過(guò)調(diào)用 kill 來(lái)殺死。如果沒(méi)有參數(shù),這個(gè)操作符殺死當(dāng)前進(jìn)程。這種情況下,kill 就像是一個(gè)不保存當(dāng)前進(jìn)程的等待表達(dá)式。如果 kill 給定了參數(shù),它們將成為進(jìn)程列表上的 delete 操作的參數(shù)。在現(xiàn)在的代碼中,kill 表達(dá)式?jīng)]有什么好說(shuō)的,因?yàn)檫M(jìn)程沒(méi)有許多的屬性來(lái)被引用。然而,更復(fù)雜的系統(tǒng)會(huì)為它的進(jìn)程附加更多的信息 時(shí)間戳、擁有者等等。默認(rèn)進(jìn)程不能被殺死,因?yàn)樗](méi)有被保存在procs?中。
通過(guò)續(xù)延模擬的進(jìn)程,其性能遠(yuǎn)不及真實(shí)操作系統(tǒng)的進(jìn)程。那么,這一章中的程序又有什么用處呢?
這些程序的用處類似于草圖。不管在探索式編程還是快速原型開(kāi)發(fā)中,這些程序其自身并不是最終目的,更多的是作為實(shí)現(xiàn)人們想法的手段。在許多其他領(lǐng)域,為這個(gè)目的服務(wù)的東西被稱為草圖。在理論上,建譯者注:可以認(rèn)為宏program 建立的由一組同時(shí)執(zhí)行的進(jìn)程組成的程序,但為與 "程序" 相區(qū)別,這里把 program 翻譯成 "進(jìn)程組"。
筑師可以在他的腦海里構(gòu)思出整棟大樓。但多數(shù)建筑師似乎在手里握著筆的時(shí)候能想得更周詳一些:一棟大樓的設(shè)計(jì)通常在一系列草圖中成型。
快速原型開(kāi)發(fā)就是給軟件作草圖。就像建筑師的第一張草圖,軟件原型往往也會(huì)由草草幾筆一揮而就。在最初把想法付諸實(shí)現(xiàn)的時(shí)候,開(kāi)銷和效率的問(wèn)題根本就沒(méi)有納入考量。結(jié)果是,在這一階段得到的往往就是無(wú)法施工的設(shè)計(jì)圖,或是低效得不可救藥的軟件。但無(wú)論如何,草圖依然有它的價(jià)值,因?yàn)?/p>
它們簡(jiǎn)明的傳達(dá)了信息
像后續(xù)章節(jié)中的程序一樣,這一章描述的程序還只是初步的設(shè)計(jì)。它僅用寥寥幾筆就勾勒出了多進(jìn)程大略
的模樣。而且,盡管它可能因?yàn)椴粔蚋咝В荒苁褂迷诋a(chǎn)品軟件中,但是它對(duì)于在多進(jìn)程的其他方面作一些嘗試還是很有用的,比如用來(lái)進(jìn)行調(diào)度算法方面的試驗(yàn)。
第 22--24 章展示了其他使用續(xù)延的例子。它們都不夠高效而不能使用在產(chǎn)品級(jí)的軟件中。因?yàn)長(zhǎng)isp 和快速原型開(kāi)發(fā)一同演化,Lisp 包含了很多專為原型開(kāi)發(fā)打造的特性:低效但是方便的功能如屬性列表,關(guān)鍵字參數(shù);推而廣之,列表也是這類特性之一。續(xù)延可以說(shuō)屬于這一類特性。它們保存了程序通常所需要的更多的狀態(tài)。所以我們基于續(xù)延的Prolog 實(shí)現(xiàn)就是一個(gè)例子,通過(guò)這個(gè)實(shí)現(xiàn)我們能很好地理解這門語(yǔ)言,但是它的實(shí)現(xiàn)方式卻是低效的。
本書更多的關(guān)注使用 Lisp 可以建立的抽象而不是效率問(wèn)題。重要的是要意識(shí)到,Lisp 既是一個(gè)適合寫產(chǎn)品軟件的語(yǔ)言也是一個(gè)適合寫原型的語(yǔ)言。如果 Lisp 有著低效的名聲,那大部分是因?yàn)槌绦騿T止步于原型。
用Lisp 寫出快速的程序很容易。不幸的是,用它寫出低效的程序更是容易。最初版本的 Lisp 程序可以像鉆石一樣:嬌小玲瓏,清澈透明,而又笨重昂貴。也許有很大的誘惑使人們就讓它保留原狀。
在其他的語(yǔ)言中,一旦你大功告成,程序能夠運(yùn)行,那時(shí)程序的效率可能就已經(jīng)可以接受了。如果你用指甲蓋大小的瓷磚來(lái)鋪地板,自然是不會(huì)浪費(fèi)多少的。習(xí)慣用這種原則來(lái)開(kāi)發(fā)軟件的人可能會(huì)發(fā)現(xiàn),克服 "程序能工作就完工" 這樣的思維有些困難。"雖然用 Lisp 你輕而易舉就能把程序?qū)懗鰜?lái)," 他可能會(huì)想,"但哥們,這些程序跑得太慢了。" 事實(shí)上,兩種看法都有問(wèn)題。你可以寫出快速的程序,但你得為此付出努力。
從這角度上說(shuō),使用 Lisp 就像生活在一個(gè)富裕而非貧窮的國(guó)度:似乎人們不得不通過(guò)工作來(lái)保持身材是種不幸,但這肯定比為了活下去而工作,自然只得消瘦下來(lái)要好。
在使用抽象能力較差的語(yǔ)言的時(shí)候,你想方設(shè)法實(shí)現(xiàn)的是功能。而在用 Lisp 的時(shí)候,你努力改進(jìn)的則是程序的運(yùn)行速度。幸運(yùn)的是,提升速度更容易一些;大多數(shù)程序只在少數(shù)幾個(gè)關(guān)鍵的地方才會(huì)關(guān)心速度。
更多建議: