淺讀 John Backus 圖靈獎獲獎演講論文

serialcoder發表於2019-02-25

上週末讀了 John Backus 的 1977 年圖靈獎獲獎演講論文,很受震撼。CS 學科依然很年輕,有些基礎性問題探索了四十多年了還沒結果。John Backus 在四十多年前提出的思想,放到現在都依然超前。我以前發表過《如何在 JS 程式碼中消滅 for 迴圈》,看起來比較離經叛道故意搞爭議。沒想到消滅 for 迴圈這件小事,早在四十多年前就有現代程式語言的祖師爺之一明確支援過了……

這篇文章只是對 John Backus 論文的淺顯介紹。我也想深度介紹一下,但功底有限。論文中的 formal proof 部分我看不懂,就跳過了;剩下部分比較好懂,但是有些上下文知識我不知道,John 的各種論斷無從擴充套件思考,我就只轉述。如果你英文過關,建議直接讀原論文。另外,筆者還只是個 CS 小學生,如果有理解偏差,還望各路大神輕噴。

一,John Backus 是誰?

在獲得圖靈獎之前,John Backus 主要的貢獻有這些:

  1. 他是 Fortran 的作者。Fortran 是第一個被廣泛使用的高階程式語言,比 LISP 還早。後來出現的大量程式語言,很多都有 Fortran 的影子。最近我在讀 Douglas Crockford 的新書 How JavaScript Works, Douglas 把 JS 裡面一些糟糕的語言特性歸罪於 Fortran 的不良影響。
  2. 他是 Backus–Naur form (BNF) 的主要發明者,這種標記方法為現代程式語言提供形式句法定義。
  3. 他領頭設計了 ALGO 語言。

他在程式語言設計上,以及在自然語言和計算機語言的互動上都有開創性貢獻,把他列為現代主流(指令式和過程式)程式語言的祖師爺之一併不為過。

他因為上述貢獻而被授予 1977 年圖靈獎。然而,在他的獲獎演講裡,他否定了自己的成就,併為發明了 Fortran 而感到遺憾……

那篇演講論文題為 Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs。這種標題放到四十年後的今天估計也會引戰的,下面我介紹下這篇論文主要講了什麼。

二,馮諾依曼架構

論文標題裡面的 von Neumann Style 指的是受馮諾依曼計算機架構影響的程式設計風格。馮諾依曼架構是上世紀四十年代,馮諾依曼為了解決當時 ENIAC 計算機的低效而提出的一種架構設計。在馮諾依曼架構下,一臺計算機包括:

  1. 一個包含控制單元和算術/邏輯單元的中央處理器。
  2. 一個記憶體單元。
  3. 一個大型儲存單元。
  4. IO 裝置

可以看出馮諾依曼架構是現代計算機的主流架構,目前的計算機也是這種架構。

John Backus 認為,馮諾依曼架構是為了解決四十年代的問題而提出的,這種架構在當時是簡潔優雅而高效的。但是到了七十年代,馮諾依曼架構要解決的問題不存在了,繼續沿用這種架構會帶來一些問題。(筆者注:既然馮諾依曼架構四十年前就過時了,為什麼到現在依然是主流?是 John Backus 錯了,還是因為 John Backus 後來解釋的那樣,馮諾依曼架構催生了馮諾依曼程式語言,而後者又綁架了計算機設計者,讓非馮諾依曼架構難以誕生?)

John Backus 認為馮諾依曼架構的問題有這些:

一,馮諾依曼瓶頸 (von Neumann Bottleneck)

在馮諾依曼架構下,程式和資料儲存在記憶體裡,處理器要通過一個管道來從記憶體裡讀取資料,計算完成後再通過管道把結果送回記憶體。這個管道就是個瓶頸。不管處理器效率多高,計算的速度取決於管道傳輸資料的速度。

二,馮諾依曼架構桎梏了程式語言

為了適應馮諾依曼架構,程式語言變得臃腫而低效。具體表現有這些:

  1. 逐字解釋程式設計風格(word-at-a-time style)。例如大家常見的各種 for 迴圈,while 迴圈和 break 指令。
  2. 句法和狀態轉移的強耦合。在指令式程式設計裡面進行狀態管理,通常要在句法層面做各種手動 housekeeping。
  3. 表示式 (expression) 和語句 (statement) 的分離。表示式是具有代數屬性的,代數表示式方便我們理解 (reason about) 程式,並且為程式提供正確性保證。但是在指令式程式設計裡面,這種代數屬性被副作用給破壞了。而語句則無序,不提供有用的數學屬性,只用來逐步執行指令。
  4. 不提供有效的組合形式。無法基於現有程式構建新的程式。注意,這篇論文發表在 1977 年,那時還沒有物件導向這個概念。我猜,如果 John Backus 知道物件導向,他也不會贊成那種程式組合方式。

三,傳統程式語言的問題

下面繼續深入前面提到的馮諾依曼風格語言的問題。

John Backus 認為,賦值語句就是語言上的馮諾依曼瓶頸。在馮諾依曼瓶頸裡傳輸的資料,很大一部分根本不是有效資料,而是這些資料的名字,以及用來計算這些名字的操作指令和資料。這無疑是低效的。更重要的是,這種程式設計風格帶來的思維的瓶頸,讓我們只能在逐字解釋的框架下去思考,不鼓勵我們用更大的概念單元 (conceptual unit) 去思考程式設計問題。(這個論斷當下依然有效)

進一步,John Backus 指出

Our fixation on von Neumann languages has continued the primacy of the von Neumann computer, and our dependency on it has made non-von Neumann languages uneconomical and has limited their development. The absence of full scale, effective programming styles founded on non-von Neumann principles has deprived designers of an intellectual foundation for new computer architectures.

如果這個論斷是正確的,John Backus 解釋了在他發表這篇論文四十多年後,我們還沒有突破馮諾依曼瓶頸的原因。

當時 LISP 已經誕生了,但是 John Backus 認為 pure LISP 還是帶著一堆傳統程式設計特徵的擴充套件。

在這篇論文裡面,John Backus 專門挑了 for 迴圈來說明馮諾依曼語言的問題。(我要是早點知道就好了 ^_^) 他給的例子如下:

c := 0 
for i := 1 step 1 until n do 
    c := c + a[i]xb[i]
複製程式碼

我不知道這是什麼語言,不過很明顯這是個 for 迴圈。John Backus 如下分析上面這段 for 迴圈的問題:

a) Its statements operate on an invisible "state" according to complex rules. 根據複雜的規則去操作看不到的狀態。

b) It is not hierarchical. Except for the right side of the assignment statement, it does not construct complex entities from simpler ones. (Larger programs, however, often do.) 程式沒有層級,沒有組合。

c) It is dynamic and repetitive. One must mentally execute it to understand it. 指令動態而重複,讀程式碼的人要在腦子裡執行一遍才懂。

d) It computes word-at-a-time by repetition (of the assignment) and by modification (of variable i). 通過重複和改變值來逐字解釋。

e) Part of the data, n, is in the program; thus it lacks generality and works only for vectors of length n. 資料和程式強耦合,難以複用。

f) It names its arguments; it can only be used for vectors a and b. To become general, it requires a procedure declaration. These involve complex issues (e.g., call-by-name versus call-by-value). 給變數命名,使程式不夠通用。John Backus 的這個主張後來即使在函式式語言的探索裡也沒被採納。這裡我不太懂。

g) Its " housekeeping" operations are represented by symbols in scattered places (in the for statement and the subscripts in the assignment). housekeeping 可以理解為具體的程式執行。for 迴圈把這些執行指令散佈在各處,難以鞏固成可通用的,單一的操作符。

針對上面提到的問題,John Backus 提議的語法如下:

Def Innerproduct 
    = (Insert +) . (ApplyToAll x) . Transpose
複製程式碼

上面的等號是三個橫線的,並不是賦值語句。

用 JS 可以藉助 Ramda 翻譯如下:

const Innerproduct = R.compose(
    R.sum,
    R.map(R.apply(R.multiply)),
    R.transpose,
)
複製程式碼

Ramda 函式的具體實現也是馮諾依曼風格的,另外賦值操作在 John Backus 的提議裡也是禁止的,這裡就忽略了,只看高階抽象部分。

John Backus 認為第二種風格的優勢如下:

a) It operates only on its arguments. There are no hidden states or complex transition rules. 引用透明,只操作引數。沒有隱藏狀態,沒有複雜的狀態轉移規則。

b) It is hierarchical, being built from three simpler functions and three functional forms. 有層級,可組合。

c) It is static and nonrepetitive, in the sense that its structure is helpful in understanding it without mentally executing it. 靜態,非重複。不用在腦子裡執行一遍程式,僅看程式結構也能理解程式意圖。

d) It operates on whole conceptual units, not words; it has three steps; no step is repeated. 以完整的概念單元為操作物件,而不是逐詞解釋。三個步驟各自獨立而不重複。

e) It incorporates no data; it is completely general; it works for any pair of conformable vectors. 程式不包含資料,完全通用。我理解的函數語言程式設計的 point free 應該符合這裡的描述。

f) It does not name its arguments; it can be applied to any pair of vectors without any procedure declaration or complex substitution rules. 不給引數命名。如上所述,目前多數語言沒有做到,這裡不做探討。

g) It employs housekeeping forms and functions that are generally useful in many other programs; in fact, only + and × are not concerned with housekeeping. These forms and functions can combine with others to create higher level housekeeping operators. 程式指令通用而可組合,可以組合成更高階的操作符。

四,馮諾依曼架構的替代

John Backus 最後提到了他那個時代一些替代馮諾依曼架構的嘗試,如 applicative machine,這種架構模型沒有儲存器和地址暫存器,也就沒有馮諾依曼瓶頸。John Backus 認為 applicative 風格程式比馮諾依曼程式更強大。這部分我不懂,就不多展開了。

John Backus 的這篇論文發表之後,學術界開始活躍探索函數語言程式設計,Haskell 的誕生就受此影響。不過,替代馮諾依曼架構的方案目前好像還遠不夠成熟。語言層面上,四十多年來有了更多的突破馮諾依曼風格桎梏的嘗試,但距離催生新的計算機設計架構還很遙遠。


插個廣告

螞蟻保險體驗與社群技術組招高階前端開發工程師。有興趣的同學聯絡 ray.hl@alipay.com

相關文章