當前位置: 華文問答 > 數碼

為什麽我們需要了解編程的歷史?

2016-01-28數碼

因為這些被遺忘的語言,還在訴說他們的故事啊。

去自然化

1978年,為了記錄下程式語言的發展,展開了會議<History of Programming Language>。

這某種意義上,算是PL的‘華山論劍’- 天下各路PL中極小數強者齊聚。

13個入選的語言中,5個有圖靈獎得主參與!(APL, Algol, Fortran, Lisp, Simula)

這等豪華陣容,只怕再也湊不出。

在這個大會上,Opening是Grace Hopper,Cobol的主刀。

我讀到這的時候,也很不解。

Cobol何德何能,為何能占著Opening,這位置上面的5個‘圖靈獎語言’那個不更有資格?

她說:「我被你們震驚到了。你們都是權威,然而我這輩子花了20年去跟權威作鬥爭!PL早期中我們最常聽到的話是‘編程電腦的唯一方法是8進制。’(省略)在那時,權威跟我們說,用電腦編程是不可能的,電腦唯一能做的只有算術。」

一個程式語言,條件判斷靠if else,自指用遞迴,組成更大的數據類別用struct。。。

這幾個構造如此天經地義,導致看上去沒有任何其他alternative:還能怎麽弄?

然而,連‘程式語言’在當時很多人眼中都是不可能,這些構造又怎麽可能‘自然’呢?

if else,是1960左右,John Mccarthy發明,並且加入Algol 60。

在此以前,Fortran只有if後面跟著一個int運算式,然後跟著三個整數-跳轉的行號!

(沒錯,goto在當時是跳轉到行號的)

當然,Fortran也一樣被‘權威’看不起,認為‘一切高級程式語言都很慢,不夠用’。

聽上去像不像C語言程式設計師嘲笑其他高級語言慢?

這就是學歷史的另一個好處:知道什麽論點會被歷史的車輪碾壓。

PL的遞迴,是同時由John Mccarthy發明的。值得一提的是,這特性差點沒進Algol 60。

至於struct,一開始只有Cobol才有,到了近8年後,到了Algol 68,才傳播到各個語言。

函數式程式設計師別得意,John自己也搞不懂啥是Lambda Calculus,到了1964 Peter Landin才指出Lambda Calculus可以用來編程。

而事實上,if else, recursion, struct, lambda,全部都有人改過,並且投入使用。

把if else改成if ... else if ... else if ... (沒有最終else),然後不規定同時滿足多個條件時,進入那個分支(非確定性),就有Dijkstra的Guard Command Language。這個語言可以由於是不確定的,可以用來組合多執行緒的blocking channel(見Concepts of Programming Languages),也可以用來對分布式,對概率程式進行推理(見Abstraction, Refinement and Proof for Probabilistic Systems)。值得一提的是,Dijkstra發明的時候,沒想這麽多,只是因為單純的‘if else不對稱,好醜’而去改。 美麗,對稱,是效用的最佳指路燈。

遞迴則可以完全去掉,改作recursion scheme(list的foldr或者相近的,但是更復雜的,構造/使用數據結構的高階函數)。用recursion scheme的人,有時候會說‘無限制的遞迴就是新的goto’。這是因為對於用了遞迴的程式,要證明這程式的內容,只能用歸納法,而如果用recursion scheme,就能用事先(透過歸納法)證明的引理去輔助證明。打個比方,如果你用list上的map,而不是手動遞迴,你寫程式的時候就能參照定理map f . map g = map (f . g)。(註:map不是recursion scheme)。到了極點,這樣可以達到,由定理的需求出發,透過函數式等價變換,由需求一步步推出最終的程式,就如同structural programming的stepwise refinement!(見Calculating Correct Compiler,The Algebra of Programming)

至於struct,在Algol 68中,就提供了Algebraic Data Type。除了‘類別T能透過給人類別X跟類別Y的值來構造’以外,還有‘類別T能透過給入類別X 類別Y的值來構造’。這跟union有點像,但是會記下是‘或的那邊’,換句話說,是disjoint union。這特性是用來描述AST(Abstract Syntax Tree)的首選,所以基本上所有鐘愛寫直譯器編譯器的靜態類別語言(如ML,Haskell)都有。

而lambda calculus中的arrow type,在linear logic(一種限制所有資源都必須使用剛好一次的邏輯系統)中,則可以拆分成兩個更細的construct:a -> b = !a -o b。其中,!x代表可以任意復制的x,x -o y代表給入一個x,消耗之,輸出一個y。!a -o b則表明,給入可以任意復制的a,消耗之,輸出一個b。由於a可以任意復制,所以消耗了不要緊,換句話說這就是給入a,輸出b。(見A Taste Of Linear Logic)這最終成為了Rust的生命周期概念。同時,在OO界,Luca由於Lambda Calculus的限制,難以用之模擬 class,object,subtyping,於是自己發明了另一套calculus - Object Calculus(見A Theory of Object)。

還有一個怪胎,APL - 這語言裏面拋棄了這些概念,一切都透過對陣列的變換來完成。(其實還是有的,不過不提倡使用)struct? ADT?用陣列。if/else?遞迴?陣列上一發操作搞定一切。類別?那是啥?

不學歷史,很容易認為這些東西生來如此,沒什麽好改的-盡管電腦的發明還不到100年!

同時,看著這些先驅開天辟地,在混沌之中創造一切,有莫名的史詩感,說不出的好受。

提取精華

等你學了點歷史,對程式語言的可塑性有更深刻理解,並且開始假想基本構造的更多形式的時候,你就到達破而後立的‘破’了。當然,破而後立,你還需要會寫Parser/Compiler/Interpreter/VM,會弄類別系統,宏,等等。。。

然而,這還不夠。

一個語言,首先是一種編程方法,一套認為人類如何編程最高效的理論,然後才是圍繞著這個理論而生的feature。世界上絕大部份(不是完全沒人用)的語言,如Algol, Cobol, Fortran, APL, C, C++, Python, Perl, Scheme, Haskell, Smalltalk, CPL。。。都有這東西。

在C++中,這叫Design Philosophy, 在Python中,稱作pythonic,Smalltalk中,是Design Principle。。。我們統稱之為Principle。沒有Principle,造出的語言,只能是舊有語言的無機混合,看上去很好,然而沒有新意,到最後,由於特性的持續積累,越來越大,導致被自己的復雜性壓垮!

Principle是一個語言的內核。有了Principle,可以用之更改過往的語言construct,得出一個全新的語言。在The Essence of Algol中,John Reynold就是從一個他認為是Algol中很重要的部份,慢慢推出各種construct,得出一個小而全的Algol。

要註意,Principle並不是為了推出Feature。相反,是Feature為Principle服務。換句話說,如果一個語言中,有跟Principle不匹配的Feature,要做減法,砍掉Feature。

比如說,Structured Programming認為,由於測試不可能保證正確性,我們需要用Hoare Logic推出程式的正確性。但是,與其正推,我們可以一步步的從終止條件倒推,到最後,從證明中取出程式(又或者說,證明就是程式)。這叫做Stepwise refinement,而Structured Programming舍棄Goto,不是因為‘Structured Programming = Programming without Goto’,而是Stepwise Refinement恰巧不支持Goto而已。盲目的去掉Goto,是治標不治本。

Functional Programming則認為,大的程式最好由小的,相互獨立的程式(兩個程式的用途都可以跟另一個分開)組合得出。如果程式A會影響程式B,就不是相互獨立的,所以FP不提倡Effect。在FP中,獨立組合性是治本,移除&控制Effect是治標。

除了Paradigm的Principle,大多語言也有各自的Principle-Smalltalk的是Design Principles Behind Smalltalk,APL的是Notation as a Tool of Thought,Algol的是The Essence of Algol。學習PL史,是發現Principle,並觀察Principle如何慢慢構造一個語言。

正本清源

除了發現Principle,並觀察Principle的發展,影響,可以反著,研究Principle如何誕生。

這樣做,可以明白一個Principle的本質,又或者可以自己照貓畫虎,搞出自己的Principle。

往往,這樣做會走出PL,甚至CS的邊界。

Logo對smalltalk有很大的影響(如Alan Kay對Personal Computing的理解就被Logo影響了,見Early History of Smalltalk),而Logo的3 Principle(Principle of Power,即學即用,Principle of Continuity,學的東西跟以往的東西有緊密連線,Principle of Culture,社區中其他人日常生活會接觸到學的東西),則來自Jean Piaget的認知發展理論,Constructivism。Logo的小烏龜,則來自於微分幾何(畫圓=向前一小步,然後左拐一點點。)而Smalltalk是Xerox Parc的精妙之作,到後來啟發了Steve, Bill。。。如果Papert沒有無理地把Lisp,Constructivism,Differential Geometry混為一體,不知道今天,電腦巨頭會是誰,你我又會用什麽東西來寫blog,看答案呢。

同時,這些Principle的來源,說不定自身就很有啟發性,找到了,就是攢到了。

比如說,說遠一些,Robert Harper發現了,就算是有bug的程式,也可以用formal verification的方式看待之。只不過,這時候證明就無法完成。然後,透過審視證明為何無法完成,就知道bug在那,而無需要寫測試瞎猜。這叫Proof Directed Debugging。

這方法,來自於數學/哲學的著作,Proofs and Refutations。這本書講了數學證明的發展史,並且論述,數學證明並不是透過Formal Proof得出的,而是透過trial and error。在此以前,我還以為數學的發展是透過Formal Proof&Informal Hole的,現在才明白除了寫證明,還有下定義,找猜想,generalize,debug證明等等,也托這的福,找到了Abductive reasoning。而這,僅僅因為我看Proof Directed Debugging的時候查查這的歷史。

一方面,查歷史就像探寶一樣,往往會在意想不到的地方跳出驚人的事件:比如說,易語言的近似物在早近30年前,就已經出現了 - 由於Algol不限字元集,在1972就有一個叫Chinese Algol的方言。當然,還有更重要的,比如說設計CPL,C的前前前身的人,據說對ISWIM有不小的影響。

另一方面, 丘處機為什麽要路過牛家村?

歷史學得越多,就會越來越發現當下的巧合,然後去幻想平行世界的展開。其中,最可惜的是,1948年就有一個跟得上60年代的語言 - Plankalkül。這是最早的,有if/struct/loop等的語言,足足超前了Algol/Lisp/Cobol/Fortran 10年!如果這語言更早被更多人知道,又或者Algol 60沒有引入遞迴,又或者John Mccarthy, Alan Kay, Dijkstra, Seymour Papert, John Backus等人改行。。