“主要的编程范型”及其语言特性关系(多图)
“主要的編程范型”(The principal programming paradigms)這幅圖,其實出現得不算早,作者在2007年完成了該圖的1.0版,到2008年更新至v1.08版本。本次提供的是翻譯成中文的版本(老實說,筆者翻譯水平相當有限,若有不當之處,請各位盡量指出,必盡快補正)。
?
這幅圖的原作者Peter Van Roy,是《Concepts, Techniques, and Models of Computer Programming》一書的作者,這本書(CTM)是少有的、能跟SICP并稱的書了。
?
該圖原文檔(PDF)下載:
http://www.info.ucl.ac.be/~pvr/paradigmsDIAGRAMeng108.pdf
?
該圖中文翻譯文件(PDF)下載:
http://www.delphibbs.com/keylife/images/u40/paradigmsDIAGRAMeng108_CN.zip
?
?
圖:主要的編程范型(點擊看大圖)
?
(圖1)主要的編程范型-縮略圖
?
一、從數據到編程
======
Peter在“主要的編程范型”中,首先分清了數據描述語言與描述式編程語言。他把數據描述語言——例如XML——作為獨立部分劃分出來,然后指出這類語言主要考慮數據結構的描象,理論上基于人們對結構(記錄/Record、結構/Struct)的理解。接下來,作者在此基礎上引入第一種“程序設計特性”,即procedure,由此得到“初階函數式編程”這一個基本的程序設計范型。
?
從這里可見Peter在程序設計語言分類上與以前的其它方法的不同之處:Peter并不在乎語言出現的時間關系,也不在乎語言的流行程度的影響。他著力于觀察語言特性發展之間的脈絡。而必須要對上述內容補充說明的是,Peter在“初階函數式編程”所說的函數,事實上是早期業界對“函數(function)”的定義,即:例程分為過程、函數兩種,二者區別僅在于前者不返回值,后者返回值。所以事實上,在這里Peter強調的是一種“普通例程”的特性。
?
二、最重要的分類依據:cells(state)
======
接下來,Peter將一個最重要的概念"state"引入進來。而這個state也是Peter對語言進行分類并考察它們的進化變化的主要依據。但是,Peter所引用的這個state概念,以及在這里使用的專用名詞“cells”相當地令人迷惑,所以在這個圖的補充說明中,Peter對此專門做了解釋:“狀態是記憶信息的一種能力,更精確地說,是及時存貯值序列的能力。”
?
你覺得這個概念象什么?對了,的確,非常象是“變量”。事實上,狀態在編程核心概念的“數據-邏輯”抽象中,它表明的正是“數據的可變性”,亦即是說:數據的可變性表現為狀態。更進一步地說:當數據被命名時,它稱為變量或常量;當數據未命名時,它成為游離的、無名稱和時序含義的存儲單元,即“cells”。這也是Peter使用“cells”這個專用名詞的原因:在本圖的討論中,需要從“變量/常量”這樣的概念中,剝離掉“未命名或命名的、確定的或非確定的,以及串行的或并發的”這三方面的性質。
?
當不考慮一個存儲位置上的命名特性時,它就既非變量、常量,也非某個確定的運算對象(例如“對象”等高級的抽象概念),而只是一種更加泛義的“狀態”,而存儲這個狀態本身的,則因為沒有位置、時序等概念,而稱為“cells”。
三、命令式語言的誕生:狀態維護
======
《程序設計語言》這本書解釋過命令式語言的本質特性,即:用算法改變數據。如果用兩個以上的邏輯(例如兩行代碼)去影響同一個存儲位置(cells),并使它的狀態改變,最終在該cell產生運算結果的,那么它就是一種命令式語言。
?
再簡單一點(但沒有上面這樣嚴謹)地說:在程序中不斷地重寫變量,變量值即是程序的最終結果。
所以,在本圖中,Peter把這個衍生關系表達為:命令式=純數據+算法+狀態維護。如圖:
(圖2)命令式語言的誕生
四、在命令式編程中對“狀態”的理解:共享狀態
======
無論是在串行的,還是并發的編程中,命令式編程范型對“狀態”的理解都是:共享狀態。在圖中,這一分支表現為全部的桃紅色(參見后圖)。
?
在串行(例如單線程)的編程中,狀態是時序相關的。因為不斷地重寫“狀態(數據/cells/變量)”,所以前一行和后一行所面對都是同一個共享狀態的不同的值/副本。在這個過程中,狀態是時序相關的,所以前一分鐘與后一分鐘的狀態是不確定的——但是在同一時刻,這個狀態是確定的。
?
與上面類似的,在多線程中,同一時刻,不同線程也將面臨這個值/副本。但是正是因為多線程(并發)中,線程A與線程B對于同一個cell,在同一時刻所得到的狀態也是不確定的——我們可以假想為多核CPU在對同一個內存地址讀寫(于是就出現了我們所謂的“同步”問題,進而也就出現了“鎖”的問題),所以在這個分支中,當加入“線程”概念之后,新的編程范型,全都變成了“觀測-非確定性”為“yes”的情況。
?
?
(圖3)觀測-非確定性(留意圖中加粗的黑框),以及狀態的共享
?
“觀測-非確定性(Observable nondeterminism)”可以被譯為“測不準”,但是用在中文中依然拗口,所以我選擇了直譯。在Peter的書(CTM)中,對該問題有一個經典的示例,即:
------------------------
declare
? C={NewCell 0}
thread
? C:=1
end
thread
? C:=2
end
------------
Peter這段代碼的意思是說:當線程1和線程2,在同時向C這個cell寫數據時,你并不確知上述代碼運行之后C這個位置上的值究竟是1,還是2。
五、解決這個問題的終極之道:不要寫cell!
======
我們顯然發現,問題是因為多個線程都在“寫cell”。在命令式的解決方案中,采用的方法是“加鎖”;持鎖存取的最經濟的方法之一是“多讀單寫”——即保證同時只有一個線程能“寫cell”。
?
但是這給應用帶來了負擔——如果一個應用程序由多個線程(分布或不分布在多個CPU核上),在它們都要讀取同一個cell、而又有某個線程要寫該cell的時候,那么大家就都要掛起來等這個寫操作完成了。整個應用程序在CPU使用(或者說效率)上就大大地打了折扣。
?
如果這只是一個桌面程序(例如記事本),大概沒人會說什么。但如果是服務器程序(例如Web Server),那么整個網絡、所有的會話就都處于等待狀態了——而這個時候,服務器的CPU占用還可能遠遠地小于1%!
?
解決問題的終極方法,就是不解決這個問題——寫cell帶來了問題,那么我們就“不寫cell”。我們由前面所有講述的內容開始倒推,問題根本是由“命令式語言”這個編程范型本身決定的:用算法改變數據。
所以,我們回到了原始的問題:如果算法不改變cell(數據/狀態/變量)呢?
?
六、函數式編程的核心概念:閉包
======
當“過程(初階的函數)”引入了“閉包”的概念后,整個“函數式語言”的體系建立了起來。所謂“閉包”,是指在函數運行的上下文中,所有的cells與函數之外無關,這些cells的狀態僅與函數——這個運算邏輯相關,則這個上下文環境“整體地”被稱為“閉包”。
?
如果換個說法,閉包就是不受函數之外影響的一堆cells。一般在實現它的時候,就是讓函數持有一個“專屬的(!)”內存塊,并且這個內存塊僅當函數本身失效的時候,才被釋放。
?
一個“函數本身”何時失效呢?如果這個函數只有傳入參數和傳出值的功能,那么這個函數就在它傳出值之后立即失效了。所以,對于整個運算邏輯——這個函數來說,它沒有操作過任何(函數之外的)狀態,既沒有讀,也沒有寫。對于這個邏輯來說,只有傳入,也只有傳出,所以它具有完全的運算確定性,它本身作為一個“結果值”來看的時候,也是確定的。
?
不過在實用時,對于上述的觀念做出了一些挑戰。例如說:當函數內部執行了一半的時候退出,怎么辦呢?答案是:1、先不釋放函數;2、過一會兒再進入。這個,就是所謂的延續(Continuation),以及在JavaScript 1.6以后推出的yield概念。同樣的,正因為“的確存在”執行之后函數不釋放的可能性,所以函數式語言通常是由語言引擎來維護閉包的生存周期的——任何新的概念、理論的提出,都必須保證這個生存周期可測、可維護。因此相較于命令式語言,自動內存管理在函數式語言中實現起來更為高效和簡潔,它最早被實現在函數式語言(LISP)語言中,也是有其必然性的了。
?
這樣一來,對于在函數之外的狀態state來說,函數從來不去影響它們。函數式編程范型帶來了新的模式:給函數數據(入口參數),函數給出結果(返回值)。即通過不斷地函數運算來產生結果,而不改變cell(數據/狀態/變量)。
?
函數式與命令式范型相比較,前者被人稱為(P,D;I,O),后者被人稱為(I,O;P,D),其中:輸入(I)、輸出(O)、加工處理程序(P)和存儲狀態數據(D)。即函數式先明確(P,D)——確定的處理與數據(閉包),然后討論在閉包上的I、O問題。而命令式先確定了I、O的方式——即state的存取問題,然后討論P、D的設計。
七、狀態的命名:變量、常量、端口等
======
在整個這個“主要的編程范型”圖中,也許最接近現實世界(通用應用開發)而又是純函數式范型的語言,就是erlang了。從圖中的標注來看,erlang具有這樣的一些語言特性:
1、消息傳遞
2、命名狀態
3、端口
4、閉包
5、線程(erlang中的輕量級進程)
?
這五個特性,其實都可以歸結為erlang對“狀態”的處理方式。其中:
1、當跨節點傳遞狀態時,采用“端口+消息傳遞”;
2、當跨線程(進程)傳遞狀態時,采用“進程ID+消息傳遞”;
3、當在多個函數/執行過程中傳遞狀態的,采用“閉包(函數入口參數)”。
亦即是說,你可以把“端口、進程標識或函數入口參數”想像成“一個傳送狀態的通道”,或者干脆是用于讀取其它外部代碼的“狀態的一個副本”的途徑。?
(圖4)狀態的命名
?
對于上述特性,erlang在整個過程中允許為狀態命名(全局的或局部的)——變量。但是,該變量只能一次賦值——向端口和進程ID傳遞消息是一次性的,而函數參數上的變量賦值/匹配也是一次性的。盡管對狀態“是否已賦值”需要進行有效性檢測,從而給系統帶來風險,但也是使得程序可讀性和應用性能變得“稍微好一些”的唯一方式。
?
在“主要的編程范型”圖中,緣于erlang對“命名的狀態”的取舍,它被歸為了“更少的說明式特性”的范疇。但同樣的原因,它也是我們目前“(相對)最易用”的工業級的純函數式語言。
?
同樣,在這個分支上還有一個后起之秀,就是“對象能力編程”。這在最近的報道中,已有基于Scala的Actor模型進行的實現。盡管Scala是在Java上的一個實現,但它擁有完整的函數式語言特性,并使用了面向對象的一些語法與思想,在加入“對象能力類型(Object Capability Types,OCT)”之后,它擁有了跨Actor傳遞對象(整個對象可以視為一個狀態/cell)的能力——在必要的時候,目標Actor會將接收到的對象再次本地化(local cell)。
八、語言的底本:LISP/Scheme/Haskell, Prolog, Fortran/Pascal, ML
======
從語言沿革的進化關系來看,Fortran位于高級語言的基底位置,基本上來說,它是現今絕大多數過程式/命令式語言的底本,在這個分支上主要發展的有Algol、Simula、Pascal與C。后來出現的各種面向對象編程語言,主要受到Simula跟Smalltalk的影響。
?
從Fortran之后,LISP成為所有函數式語言的底本,但由于早期LISP的應用性極差,所以Scheme吸取了Algol與LISP的一些特性,成為函數式語言發展的主要力量之一。在1987年之后,從Scheme、Smalltalk等演化出來的Haskell,也成為不可小視的新生力量。
?
(圖5)函數式語言的龐大家族
?
Prolog這個語言的獨特的開創性在于:它主要描述通過邏輯事實進行推論并得出結論的過程,它的運算對象并非直接含義上的“數據”,而是一種接近人的思維的抽象:邏輯子句。這種新的、對數據的抽象開啟了一個語言分支。
?
在圖中幾乎總是與Scheme一起出現的ML(Meta Language)是一種,它的一個比較普及的、方言化的實現是SML(Standard ML)。在這個語言分支上,一個更晚出現而又頗富盛名的語言是Caml/OCaml(Caml, Categorical Abstract Machine Language)。基本上,它看起來象是在ML的函數式語言特性上加上:
1、命名(持久性常量)
2、cell
Objective Caml (OCaml)支持面向對象編程,而使它更為眾所周知的是:ECMAScript Ed4(Javascript 2)的標準規范,是采用OCaml這個語言來“定義”——并同時實現的。而微軟的F#也是基于OCaml在函數式語言上方向上發展的。
?
另一個函數式的分支是數據流編程,它的底本語言是Dataflow——圖中出現的Pipes/MapReduce是兩種不同的機制,而非確定的語言。在這個分支上,現實中主要采用數據流編程范型的語言其實是LabVIEW。相對于數據流(驅動的)編程來講時,命令式語言也被稱為控制流(驅動的)編程語言。數據流編程在函數式這個分支中也是有開創意義的,它帶來了圖化、網絡圖化編程這樣一種格局。
?
在這一小節(以及圖)里面,主要提及到的底本語言的出現時間為:
??? * 1957 FORTRAN
??? * 1958 ALGOL
??? * 1960 LISP
??? * 1962 SIMULA
??? * 1969 Smalltalk
??? * 1970 Prolog
??? * 1971 Dataflow
??? * 1972 C
??? * 1973 ML
??? * 1975 Pascal
??? * 1975 Scheme
??? * 1990 Haskell?
??? * 1996 OCaml
九:延伸閱讀
======
《象大師們一樣思考》
http://blog.csdn.net/aimingoo/archive/2008/10/09/3037952.aspx
《世界需要一種什么樣的語言》
http://blog.csdn.net/aimingoo/archive/2009/03/12/3983975.aspx
《表面的簡潔》
http://blog.csdn.net/aimingoo/archive/2009/04/22/4099931.aspx
?
《無廢話JavaScript》(上)(下)
http://blog.csdn.net/aimingoo/archive/2008/10/06/3022379.aspx
http://blog.csdn.net/aimingoo/archive/2008/10/06/3022409.aspx
from:?http://blog.csdn.net/aimingoo/article/details/4648284
總結
以上是生活随笔為你收集整理的“主要的编程范型”及其语言特性关系(多图)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从“架构师书单”讲开去
- 下一篇: 我的程序语言实践