引言
知識和智慧都能拓展人類的能力。知識帶來了電腦,智慧帶來了筷子。不幸的是,我們目前與前者有過多的關聯。後者則得等待一個更為昇華的日子。
Turing 的聲譽是奠基於什麼?是他在證明通用計算設備(後來被稱為「Turing machine」)存在無法計算的函數定理嗎?我對此表示懷疑。更有可能的是,他的聲譽來自於他發明和使用的模型:他的形式化機制。
這個模型抓住了整整一代科學家的想像,並激發了他們的思想。它為導致理論誕生的論證提供了基礎。他的模型被證明如此有用,以至於其產生的活動不僅分佈在數學領域,也滲透到多個技術領域。所採用的論證並不總是形式化的,而隨之產生的創造也並非全屬抽象。事實上,Turing machine 最豐碩的成果之一在於可計算函數的創建、研究與計算,即在電腦程式設計中。這並不令人意外,因為電腦能計算的東西遠比我們目前已知如何指定的多得多。
我相信大家都會同意這個模型具有巨大的價值。歷史會原諒我在這次演講中沒有著墨於 Turing 對通用數字電腦發展的影響,通用數字電腦的發展進一步加速了我們對計算理論和實踐的投入。
自 Turing 模型出現以來,當然也出現了其他對我們計算領域有關注並帶來益處的模型。然而,我認為只有一個模型的影響力與 Turing 模型一樣大:那便是稱為 ALGOL 的形式化機制。許多人會立刻表示異議,指出我們當中很少有人理解或使用過它。雖然令人遺憾的是事實如此,但這並非重點。ALGOL 對計算機科學研究發展所帶來的推動力才是關鍵,其使用者人數則不重要。ALGOL 也激發了我們的思想,並為我們的論證提供了基礎。
我長久以來一直思索為何 ALGOL 在我們領域是如此有用的模型。也許部分原因在於:
(a) 它的國際贊助; (b) 其語法在印刷品中的清晰描述; (c) 它自然地結合了組譯程式和次程式設計中重要的程式特性; (d) 語言本身自然可分解,使得人們可以建議並定義對語言部分進行相當廣泛的修改,而不會破壞其結構和符號令人印象深刻的和諧。短語「類 ALGOL (ALGOL-like)」經常在關於程式設計、語言和計算的論證中使用,具有一種受人認可的實質意義。ALGOL 似乎是一個持久的模型,甚至在經過手術(無論是探索性的、整形的還是截肢的)後仍然蓬勃發展; (e) 事實上,它對於我們希望程式設計的許多任務來說,是令人心癢難耐地不適用。
有一點我很確定:ALGOL 的神奇並非歸功於它的誕生過程:委員會制定的。因此,當類似方式「受精」的蛋孵化出更為乏味的模型時,我們不應該感到失望。後者雖然展現了相對於 ALGOL 令人印象深刻的改進,卻只會引發我們集體想像的呵欠。這些可能是對 ALGOL 的改進,但它們並不是模型的繼承者。
自然地,我們應該並且也確實在善加利用它們提供的改進來糾正 ALGOL 的不足。同時我們也應該反思,為什麼它們未能激發我們的創造力。我們應該問,為什麼在它們的影響下,計算機科學研究甚至計算實踐只能前行,而無法躍進?我不敢說自己知道全部答案,但我確信它們乏味的一個重要原因在於將注意力集中在 ALGOL 的錯誤弱點上。
語言與資料結構的綜合
我們知道,設計語言是為了簡化某一類重要問題所產生的大量算法的表達。只有當這類問題的算法,在經過一些培養後,預計會給電腦帶來大量的處理負擔,並導致程式設計師使用現有語言時花費大量的編寫時間,才應該進行語言設計。因此,語言必須降低一組交易的成本,以彌補其設計、維護和改進的成本。
後續語言的誕生有各種原因:
(a) 糾正給定語言中的錯誤、遺漏或冗餘會引出自然的重新設計,從而產生更優越的語言。 (b) 糾正給定語言中的錯誤、遺漏或冗餘需要重新設計才能產生有用的語言。 (c) 通常可以從任意兩種現有語言創建第三種語言,這種語言 (i) 以整合形式包含這兩種語言的功能,並且 (ii) 其語法和評估規則比兩者語法和評估規則的總和更簡單。
考慮到以上幾點,我們從何處開始綜合一個後續模型,這個模型不僅能改進與機器的交流,還能將我們的注意力集中在計算領域的重要問題本身?
我相信自然的起點必須是資料的組織和分類。至少可以說,不了解資料的性質是很難創建算法的。當我們試圖在程式語言中表示一個算法時,我們必須先知道該算法的資料在該語言中的表示方式,然後才能希望能進行有用的計算。
由於我們的後續語言將是一個通用程式設計語言,它應該擁有通用的資料結構。這句話從不同的角度看,既不像你想像的那麼難,也不像你想像的那麼容易。這種擁有方式應該如何安排?讓我們看看我們現有的語言中做了什麼。那裡的方法如下:
(a) 語言中定義了一些「基本」資料結構,例如:整數、實數、類型齊次的陣列、列表、字串和檔案。 (b) 在這些結構上提供了一套「足夠」的操作,例如:算術、邏輯、提取、賦值和組合。 (c) 任何其他資料結構都被視為非基本結構,必須通過基本結構來表示。非基本結構中固有的組織通過對基本資料的操作明確提供,例如,複數實部和虛部之間的關係通過實數算術來表示。 (d) 為這些非基本資料結構提供的一套「足夠」的操作被組織為程序。
這種擴展過程無可厚非。每個程式設計語言都必須允許其方便使用,因為最終總是需要如此。然而,如果這種擴展過程被過度使用,算法往往無法展現它們實際具備的結構清晰性。更糟糕的是,它們往往會比必要執行得更慢。前一個弱點是因為語言為算法的定義方式錯誤,而後一個弱點則是因為語言強制對資料進行過度組織,並要求在執行過程中進行本來可以在「算法」執行前一次性完成的管理工作。在這兩種情況下,變數都被語法和評估規則在錯誤的時間綁定。
我想我們所有人都意識到我們的語言沒有足夠的資料類型。當然,在我們的後續模型中,我們不應該試圖通過添加幾個來彌補這個缺點,例如,有限數量的新的類型和一個通用的包羅萬象的結構。
我們定義函數的經驗應該告訴我們該怎麼做:不要將注意力集中在通用級別上完整的一套定義好的函數,而是在語言內部提供結構和控制,從而實現程序內函數的高效定義和使用。
因此,我們應將注意力集中在我們的後續模型中,提供定義資料結構的方法。但這本身還不夠。「足夠」的附帶操作集、它們發生的上下文以及它們的評估規則也必須在指定資料結構的程式內部給定。
必須為資料結構提供的某些能力的列表包括:
(a) 結構定義; (b) 將結構賦予標識符,即給標識符分配資訊單元; (e) 在給定結構的情況下,命名部分的規則; (d) 將值賦予附加到標識符的單元; (e) 引用標識符附加單元的規則; (f) 組合、複製和清除結構和單元內容的規則。
這些能力現在肯定在大多數語言中以有限的形式提供,但通常在它們的語法和評估規則中過於固定。
我們知道,語言的設計者無法固定有多少資訊會存放在結構中,又有多少會存放在結構攜帶的資料中。必須允許每個程式進行自然的選擇,以實現時間和存儲之間的理想平衡。我們知道表示陣列、列表結構、字串或檔案或其組合的方式並非只有一種。選擇取決於:
(a) 存取的頻率; (b) 給定資料所嵌入的結構變化的頻率,例如,向檔案追加新的記錄結構或邊界陣列; (c) 電腦存儲需求中不必要的大量佔用成本; (d) 存取資料中不必要的時間成本;以及 (e) 能夠有序增長的算法表示的重要性,以便結構始終清晰。
天知道,這些選擇對於程式設計師來說是困難的。在設計層面上做這些選擇當然是不可能的。
資料結構不可能憑空產生。事實上,我們通常採用的方法是使用具有固定、基本資料結構的後台機器。這些結構是與真實電腦相關聯的,儘管後台機器在定義資料結構方面可能更為抽象。一旦選擇了後台機器,我們的定義所需的額外結構必須表示為資料,即作為指向結構的名稱或指標。並非所有指標都引用相同類型的結構。由於程式段本身就是結構,像「程序標識符內容 of (x)」這樣的指標建立了一類變數,其值是程序名稱。
常數與變數
確實,語言的靈活性取決於允許程式設計師在編寫或執行過程中變更的程度。在語言中系統地發展變更能力是程式設計中的核心問題,因此也是我們後續語言設計中的核心問題。我們的經驗總是呈現特殊情況,由此我們建立新變數的定義。每一個新的經驗都促使我們關注對更廣泛通用性的需求。分時(Time sharing)是我們新的經驗之一,很可能成為一種習慣。分時促使我們關注系統的管理以及程式設計師在執行前、執行中和執行後對其程式碼的管理。與程式的互動將變得越來越靈活,我們的後續語言不應該讓這變得困難。我們對對話式程式設計的願景不僅僅是快速的回應時間和便捷的偵錯輔助:我們最有趣的程式從來都不是錯誤的,也從來都不是最終版本。作為程式設計師,我們必須在希望為對話式程式設計提供適當的語言模型之前,先隔離出對話式程式設計的新穎之處。我認為,新的要求是使我們以前視為固定的東西在我們的語言中變得可變。我現在指的不是新的資料類別,而是那些其值是程式或程式的一部分、語法或語法的一部分以及控制方式的變數。
我們目前大部分注意力都放在開發用於管理檔案的系統上,這些系統改進了整個系統的管理。相對較少關注於改進計算的管理。前者可以在我們編寫程式的語言之外完成,而後者則需要我們在使用來解決問題的程式語言中改進對變異性的控制。
在處理程式文本時,一段文本的出現可能在文本中只出現一次,但卻執行多次。這就產生了識別常量和變量的需求。我們通常會將具有變數形式的東西通過初始化過程使其成為常量;並且我們經常允許這個過程本身可以被重複。這個初始化過程是一個基本過程,我們的後續語言必須有一種系統化的方法來處理它。
讓我們考慮 ALGOL 中一些初始化和變更的例子:
(a) 進入區塊。進入區塊時,宣告會進行初始化,但只涉及標識符的一些屬性。因此,integer x
初始化了身為整數的屬性,但無法初始化 x
的值使其在區塊的作用域內不變。宣告 procedure P (...) ; ... ;
強調初始化了標識符 P
,但在區塊內無法改變它。array A [l:n, l:m]
被賦予一個初始結構。無法初始化其單元的值,也無法改變附加到標識符 A
的結構。
(b) for 語句。這些表達式,我將稱之為步長(step)和直到(until)元素,無法初始化。
(c) 程序宣告。這是對程序標識符的初始化。在程序調用時,其形式參數像程序標識符一樣被初始化,甚至它們的值也可以被初始化。然而,不同的調用會建立形式參數標識符的不同初始化,但不是值的不同初始化模式。
ALGOL 中允許形式和值綁定到標識符的選擇一直被認為是足夠的。然而,如果我們將形式的賦值、形式的評估和初始化視為要在語言中理性指定的、重要的函數,我們可能會發現 ALGOL 在可用選擇方面是有限甚至反覆無常的。我們應該期望後續語言會遠 less arbitrary 和有限。
讓我舉一個簡單的例子。在 for 語句中,使用一個構造,例如 value E
(其中 E 是一個表達式),作為步長元素,將會標示表達式 E 的初始化。value
是一種運算符,控制將值綁定到形式。每個運算符的應用都有一個自然的作用域。
我提到過程序標識符通過宣告進行初始化。然後,通過賦值可以改變程序到標識符的附加。我已經提到如何通過指標來實現這一點。當然,還有其他方法。最簡單的方法是完全不改變標識符,而是擁有一個選擇索引,從一組程序中選擇一個。初始化現在定義了一個形式陣列,例如 procedure array P [l:k] (.fl, f2,... ,fj); ... begin... end;... ; begin... end;
。調用 P [~] (al, a2, . .., as)
將選擇第 i 個程序體來執行。或者,可以定義一個程序開關 P := A, B, C
和程序指向表達式,以便上述調用選擇第 i 個程序指向表達式來執行。上述方法對於某些應用來說過於靜態,並且它們缺乏賦值的一個重要屬性:能夠確定何時一個賦予的形式不再可存取,以便其存儲可以另作他用。這類程序(即動態賦予的程序)的一個可能應用是作為生成器。假設我們有一個程序用於計算 (a) $\sum_{k=0}^N C_k(N)X^k$ 作為函數 (b) $f(x) = \sum C_kX^k$ 的近似,其中整數 N 被指定。現在,一旦找到 $C_k(N)$,我們僅對評估 (a) 對於不同的 x 值感興趣。然後我們可能希望定義一個程序,它從 (b) 準備 (a)。這個程序在其首次執行時,將計算 (a) 的程序賦予自身或另一個標識符。後續對該標識符的調用將只會產生這個創建的計算。這種動態賦值帶來了一些吸引人的可能性:
(a) 由於第二次賦值,程序的部分存儲可以釋放。 (b) 資料存儲可以分配給定義被創建的程序標識符作為其 own 屬性。 (c) 初始調用可以修改結果定義,例如,在初始調用中通過名稱或通過值調用形式參數將影響獲得的定義種類。
很容易看出,我所要表達的重點是必須對賦予標識符的形式和值進行統一的初始化和變更方法。這是計算過程的一個要求。因此,我們的後續語言必須具備一個通用方法來控制其標識符類別的初始化和變更操作。
我們希望在對話式程式設計中執行的一個操作是系統地、或受控地修改資料和文本的值,這與在偵錯中發生的非系統修改不同。執行這類操作顯然意味著文本的某些部分被理解為是可變的。我們 again 通過宣告、初始化和賦值來實現這一點。因此,我們可以在區塊標頭中寫下如下宣告:
real x, s;
arithmetic expression t, u;
在伴隨的文本中,s := x + t;
的出現導致賦予 t
的算術表達式的值(例如通過輸入獲得)與 x
的值相加,結果作為 s
的值賦予。我們注意到 t
可能已經作為形式被輸入和存儲。運算符 +
只有在應用了適當的轉換函數後才能完成。表達式的一部分翻譯只能在經典的「翻譯時」完成,這一事實不應阻礙我們。是時候我們開始系統地面對部分翻譯的問題了。可變的自然文本部分是由語言的語法單元標識的。
安排非預期的程式變更則較為困難。主要問題在於如何在原始文本中識別要變更的文本,以及如何在實際評估的文本中找到其在翻譯過程中的對應部分。說「解釋執行原始文本」很容易,但滿意的成本平衡點在於翻譯和解釋之間的中間解決方案。在下一節中,我想表達一個觀點,它可能為如何在每個程式需要時實現這種平衡提供一些啟示。
資料結構與語法
即使列表結構和遞歸控制不會在我們的後續語言中扮演核心角色,它仍然將在很大程度上受益於 Lisp。這種語言在程式設計師之間引發幽默的爭論,經常因同一特點而被批評和讚揚。我只想在此指出,其描述有意識地以比我所知任何語言都更清晰的方式揭示了語言定義的適當組成部分。Lisp 的描述不僅包括其語法,還包括其語法在語言中作為資料結構的表示,以及環境資料結構在語言中也作為資料結構的表示。實際上,後者的描述在某種程度上有所保留,但並非根本性的問題。從上述描述中,可以通過使用一些基本函數,將評估過程描述為一個 Lisp 程式。雖然這種描述的完整性對於其他語言也是可能的,但通常不被視為其定義描述的一部分。
對 ALGOL 的檢查顯示,其資料結構不適合表示 ALGOL 文本,至少對於描述該語言的評估方案而言並不適用。對於描述 ALGOL 程式的環境資料結構而言,情況也是如此。
我認為至關重要的是,我們的後續語言必須達到平衡,擁有適合表示語法和環境的資料結構,以便評估過程可以在語言中清晰地陳述。
為什麼給出這樣的描述如此重要?僅僅是為了給語言附加「閉包」的優雅屬性,以便可以組織引導(bootstrapping)嗎?幾乎不是。它是系統構建能夠進行對話式計算的程式系統的關鍵。
程式語言具有語法和一套評估規則。它們通過將程式表示為資料(評估規則適用於這些資料)來連接。這個資料結構是語言的內部或評估導向的語法。我們以外部語法編寫程式,為人類交流的目的,我們固定外部語法。內部語法通常被認為與翻譯器和機器密切相關,以至於幾乎從未在文獻中描述過。通常存在一個翻譯過程,將文本從外部語法表示轉換為內部語法表示。實際上,內部描述的變化更根本地與評估規則相關聯,而不是與要執行的機器相關聯。評估規則的選擇在很大程度上取決於語言變數的綁定時間。
這為處理變化的文本提供了一種組織評估的方法。由於內部資料結構反映了正在處理的文本的變異性,讓翻譯過程選擇語法的適當內部表示,而通用的評估器根據選擇的語法結構選擇特定的評估規則。因此,我們必須在外部語法中提供指示變數的線索。例如,宣告 arithmetic expression t; real u,v;
以及語句 u := v/3*t;
表示 v/3
和 t
的值可能具有不同的內部語法。應該指出,t
的行為非常類似於 ALGOL 的形式參數。然而,對賦值的控制較少受到限制。我認為這僅僅表明形式-實際參數賦值獨立於封閉次程式的概念,並且它們在程序構造中被結合,作為指定初始化作用域的一種方式。
在非預期的變更情況下,了解內部語法結構可以使文本變更時的重新翻譯和評估規則更改達到最少。
由於必須完全在某種語言中檢查和構建資料結構和評估規則,所以似乎合理的是使用源語言本身。可以定義一個內部語法作為翻譯的目標,其字元串是源語言中允許的字元串的子集。這樣的語法,如果選擇接近機器碼,就可以由非常類似於機器的規則進行評估。
雖然我一直輕鬆談論附加到語言標識符的變異性,但我卻沒有提到控制的變異性。我們實際上沒有描述控制的方法,因此我們無法宣告其方式。我們應該期望我們的後續語言具備 ALGOL 所擁有的控制種類——以及更多。平行操作是一種正在進行大量研究的控制方式。另一種剛開始出現在語言中的是分布式控制,我將稱之為監控。處理 A 持續監控處理 B,以便當 B 達到某個狀態時,A 會介入控制處理未來的活動。A 內部的控制可以寫成 when P then S;
;P
是一個謂詞,它總是在某些定義的作用域內被測試。只要 P
為真,正在監控的計算就會被中斷,並執行 S
。我們希望通過僅在執行可能使 P
為真的動作時測試 P
來實現這個構造。然後,在定義語言、環境和評估規則時,我們必須包含在執行期間可以監控的狀態。通過程式設計,可以從這些基本狀態構建其他狀態。了解這些基本狀態後,甚至可以在程式中定義具體謂詞之前,就可以安排在可能的地方插入測試。這樣,我們就可以偵錯我們的程式,而無需干擾程式本身。
語法的變化
在單一語言的範圍內,可以實現驚人的變異性。然而,所有經驗告訴我們,不斷變化的需求將對語言本身施加越來越大的壓力,促使其改變。設計者無法預測這些變化的精確性質,因為它們是尚未解決的問題中尚未編寫的程式的結果。諷刺的是,最有用和最成功的語言最容易受到這種改變壓力的影響。幸運的是,預期的早期變化種類在某種程度上是可預測的。因此,在科學計算中,數字的表示和算術會變化,但表達式的性質除了通過其運算元和運算符之外不會改變。由這些來源引起的語法變化很容易處理。實際上,算術表達式的語法和評估規則在語言中被保留為未定義。相反,語言中提供了用於程式設計算術表達式定義的語法和評估規則,並設定了這些定義的作用域。
這種「一夜情」語言定義遊戲中唯一的真正困難是評估規則的規範。它們必須謹慎給出。例如,以這種方式引入矩陣算術時,矩陣表達式的評估應注意臨時存儲的使用,並且不執行不必要的迭代。
一種在定義使用中採用的自然技術是從語言 X 開始,將定義視為將語法擴展到語言 X',並給出評估規則作為一種歸約過程,將 X' 中的任何文本歸約為 X 中等效的文本。
應當指出,語法的變化需要語法的表示,最好是作為 X 本身的一個資料結構。
結論
程式語言圍繞著變數構建——其操作、控制和資料結構。由於這些概念是所有程式設計所共有的,通用語言必須專注於它們的有序發展。儘管我們對 Turing 及其簡單模型深感感激,他的模型也關注了重要的概念,但我們毫不猶豫地使用比他認為必需的更複雜的機器和資料。程式設計師不應該滿足於那些允許他們編寫一切但無法輕鬆編寫有趣內容的語言。因此,我們的進步是以我們在效率和通用性之間達到的平衡來衡量的。
隨著我們與計算的關聯性質改變——並且確實如此——語言的適當描述也會改變;我們的重點會轉移。我認為我們的後續模型將展現這種變化。計算機科學是一個不安分的嬰兒,它的進步既依賴於觀點的轉變,也依賴於我們當前概念的有序發展。
這裡提出的任何想法都不是新的;它們只是時不時地被遺忘。
我想感謝 Association 賦予我發表這第一次 Turing 演講的榮譽。結束這場演講還有什麼比說「如果 Turing 今天在這裡,他將會以不同的方式在一個不同名稱的演講中發表意見」更好的方式呢?