官术网_书友最值得收藏!

序言

在學生時代,遇到妙趣橫生的Alan Perlis時我總是非常興奮,并和他有幾次交談。他和我對兩種截然不同的程序設計語言——Lisp和APL——都非常喜愛和珍重。跟上他的步伐很不容易,何況他還在開辟卓越的新路。我想重新檢視他在給這本書原序中的一個論斷(我建議你在讀完本序言后再重讀他的序,它就印在后面):用100個函數在一種數據結構上操作,優于用10個函數在10種數據結構上操作。這句話真對嗎?

為了認真回答這個問題,我們首先要問,這一種數據結構是“普適的”嗎?它能方便地取代那10種更特殊的數據結構的角色嗎?

對于這些,我們還可以問,我們真的需要這100個函數嗎?是否存在一個單一的“普適”函數,它能取代所有其他函數的角色?

對最后這個問題,有一個令人驚異的回答:是!只需要不多的技巧就能構造出一個函數,該函數接受(1)一種數據結構,它可以看作其他函數的描述,以及(2)一系列的參數,使該函數的行為恰如前面的那些函數作用于這些給定的參數。同時,只需要不多的技巧就能設計出一種數據結構,使其足以描述我們需要的所有計算。一種這樣的數據結構(用于表示表達式和語句的帶標簽表,加上記錄名字與值的關聯的環境)和一個這樣的普適函數(apply)將在本書的第4章介紹。也就是說,我們可能只需要一個函數和一種數據結構。

這些在理論上都是對的,但在實踐中,我們發現區分不同事物確實很有幫助。作為人,在需要構造計算的描述時,應該把我們代碼的結構組織好,使我們更好地理解它們。我相信Perlis在這里不是想討論計算能力的問題,而是要討論人的能力及其限度。

人的頭腦似乎在為事物命名方面做得很好。我們有強大的關聯記憶,給我們一個名字,我們可以很快想起與之關聯的某些事物。這可能就是我們會發現使用lambda演算很方便,而使用組合演算就不太容易的原因。對大多數人而言,給他們解釋Lisp表達式(lambda(x)(lambda(y)(+x y)))或JavaScript表達式x=> y=> x+y都比較容易,而解釋下面的組合表達式就難得多了:

((S((S(K S))((S((S(K S))((S(K K))(K+))))((S(K K))I))))(K I))

即使將其寫成結構與之對應的5行Lisp代碼,事情也不會變得更容易。

所以,雖然從原則上說,我們可以只用一個普適函數,但卻更應該把代碼模塊化,并為其中的各種片段命名。然后在這個普適函數里用名字來說這些函數的描述,而不是簡單地把這些函數的描述直接塞進代碼里。

我在1998年的演講“生長出一種語言”里曾經說過,一個好程序員“并不只是寫程序,好程序員要構造有用的詞匯表”。隨著設計和定義出程序中越來越多的部分,我們要給這些部分命名,這樣做的結果就是生長出了一個日益豐富的、能用于描述其他部分的語言。

然而我們也發現,與區分不同的數據結構相比,為它們命名更不容易。

嵌套的表可以看作一種普適數據結構(值得說一下,很多時髦的、使用廣泛的數據結構,例如HTML、XML和JSON,也都是各種括號括起的嵌套表示形式,只不過比Lisp的簡單括號形式更精致一點)。還有許多函數在范圍廣泛的許多不同情景下都很有用,例如確定一個表的長度,把一個函數應用于一個表的每個元素并返回結果的表等。因此,當我思考某項特殊的計算時,就經常對自己說,“這個以兩個東西為元素的表,我期望它表示一個人的姓和名;那個以兩個東西為元素的表,我期望它表示一個復數的實部和虛部;另一個以兩個東西為元素的表,我期望它表示一個分數的分子和分母”,如此等等。換句話說,我對它們做了區分——因此,明確地表示這些數據結構之間的區分,也可能非常有用。一種作用就是可以防止一些錯誤,例如無意中錯誤地把復數當作分數使用。(再次強調,這一注釋也是有關人的能力及其限度。)

在寫本書的第1版時,那是在大約40年前,許多數據組織方式已經成為相對的標準,特別是“面向對象”技術。還有很多程序設計語言(包括JavaScript)支持一些特殊數據結構,例如對象和字符串、堆和映射,還有許多內置的或者庫支持的數據機制。但是,在這樣做的同時,許多語言放棄了對更一般、更普適的描述機制的支持。以Java為例,開始時它不支持函數作為一等元素,新近才把這種功能結合進來,這極大地提高了表達能力。

類似地,APL原來也不支持函數作為一等元素,而且它原本只支持一種數據結構——各種維數的數組。以數組作為普適數據結構非常不方便,因為數組不能以其他數組作為元素。APL的新近版本也支持了匿名的函數值和嵌套的數組,這些擴充奇跡般地增強了APL的表達能力。(APL的原初設計確實包含了兩個非常好的特點:它有一集容易理解的函數,它們都應用于唯一的一種數據結構,這些函數還被特別選擇了一套名字。我這里不是說那些奇怪的符號或希臘字母,而是APL程序員在使用函數時所用的詞匯,例如shape、reshape、compress、expand和laminate等。這些都是名字而不是符號,它們說明了函數的功能。Ken Iversion在為操作數組的函數取短小易記而且生動的名字方面確有些高招。)

至于JavaScript,與Java類似,最初設計時心里想的就是對象和方法,但一開始就納入了一等函數,用它的對象定義普適數據結構也沒有任何困難。由于這些情況,JavaScript與Lisp的距離不像你想象的那么遠。因此,正如這一版《計算機程序的構造和解釋》展示的,它可以作為表達相關核心思想的另一個框架。SICP并不是討論某種程序設計語言的,它展示的是有關程序組織的一些強大且具有普適性的思想,因此應該適用于任何程序設計語言。

Lisp和JavaScript有哪些共性?把一項計算(代碼加一些相關數據結構)抽象為一個可用于在將來執行的函數的能力;針對一些參數調用函數的能力;劃定一些不同情況(條件執行)的能力;一種方便的普適數據結構;對數據的完全自動化的存儲管理(這種功能初看起來無足輕重,好像什么都沒說,直到你認識到許多廣泛使用的程序設計語言都缺少了這種功能);很大一集操控普適數據結構的非常有用的函數;以及使用普適數據結構去表示各種更特殊的數據結構的一套標準策略。

因此,真理很可能位于Perlis雄辯地設置的兩個極端之間。甜蜜點可能更像是某種情況,例如針對一種普適數據結構(例如表)完成各種操作的40個足夠普適的有用函數,再加上10組每組各6個函數,分別用于操控該普適數據結構的10種特殊視角。

當你閱讀這本書時,請不要只關心各種程序設計語言的結構及其使用,還應該關注賦予各個函數、變量和數據結構的名字。這些名字并不都簡短并生動,如Iverson為其APL語言選的名字。但它們也經過精心地、系統化地選擇,可以加深你對整個程序結構的理解。

基本操作、組合方法、功能抽象、命名,以及為了做出各種區分而使用的普適數據結構的各種常用定制方法,這些都是一個好的程序設計語言的基本構造要素。從這些出發,再加上想象和基于經驗的良好的工程評判能力,就能做好其他事情。

Guy L.Steele Jr.

馬薩諸塞州列克星敦,2021

主站蜘蛛池模板: 兴和县| 平利县| 三都| 长治县| 黄冈市| 丰顺县| 文安县| 东台市| 胶南市| 镇平县| 汝阳县| 保康县| 武山县| 扬州市| 盖州市| 德钦县| 许昌县| 灵寿县| 霍州市| 墨玉县| 崇左市| 澄迈县| 芜湖县| 昌宁县| 镇坪县| 高青县| 布尔津县| 邢台市| 茂名市| 灯塔市| 海口市| 内乡县| 疏附县| 青浦区| 蒙自县| 商都县| 厦门市| 兴海县| 九江市| 麻栗坡县| 湾仔区|