對(duì)象已死?
最近常有一種說(shuō)法,就是我們?nèi)缃衩媾R著另外一場(chǎng)編程模型的變革,面向?qū)ο蠹夹g(shù)已經(jīng)處在被淘汰的邊緣,函數(shù)式語(yǔ)言會(huì)取代面向?qū)ο蠹夹g(shù)成為主流方式,甚至出現(xiàn)了面向?qū)ο笠阉赖难哉?。作為一個(gè)硬核函數(shù)語(yǔ)言的狂熱者,我個(gè)人當(dāng)然希望函數(shù)式語(yǔ)言可以一統(tǒng)天下,成為主流之選。但是不是應(yīng)該把對(duì)象技術(shù)和函數(shù)技術(shù)對(duì)立起來(lái),說(shuō)式后者取前者而代之,我個(gè)人認(rèn)為,這和如何看待面向?qū)ο蠹夹g(shù)有關(guān)。
做為工程實(shí)踐的對(duì)象技術(shù)
在這個(gè)年代,大家有一種神圣化面向?qū)ο蠹夹g(shù)的傾向,很多人都把對(duì)象技術(shù)奉為高深的思想和理論。但實(shí)際上,面向?qū)ο蠹夹g(shù)僅僅一種工程實(shí)踐而已,它是依托于其他技術(shù)而存在的一種實(shí)踐,本身并不是一種完備的計(jì)算模型。
在計(jì)算機(jī)科學(xué)發(fā)展的早期,對(duì)于計(jì)算機(jī)的非數(shù)值計(jì)算應(yīng)用的討論,以及對(duì)于可計(jì)算性問(wèn)題的研究和發(fā)展,大抵確立了幾種的計(jì)算模型:遞歸函數(shù)類、圖靈機(jī)、Lambda演算、Horn子句、Post系統(tǒng)等等。其中遞歸函數(shù)類是可計(jì)算性問(wèn)題的數(shù)學(xué)解釋;Horn子句是prolog這類邏輯語(yǔ)言的理論基礎(chǔ);lambda演算成為了函數(shù)式語(yǔ)言的理論基礎(chǔ);圖靈機(jī)是圖靈解決可計(jì)算問(wèn)題的時(shí)候所設(shè)計(jì)的裝置,其后成為計(jì)算機(jī)的裝置模型,與圖靈機(jī)相關(guān)的自動(dòng)機(jī)以及馮諾依曼結(jié)構(gòu),成為了命令式語(yǔ)言的理論基礎(chǔ)。
因此當(dāng)我們談及函數(shù)語(yǔ)言和命令式語(yǔ)言優(yōu)劣的時(shí)候,我們實(shí)際上是在討論其背后的計(jì)算模型——也就是lambda演算和馮結(jié)構(gòu)裝置操作——在執(zhí)行效率和抽象層次上的優(yōu)劣。
而面向?qū)ο蠹夹g(shù)則比較尷尬了,其背后沒(méi)有一個(gè)對(duì)應(yīng)的計(jì)算模型(80年代的時(shí)候曾有人研究過(guò),Pi演算是個(gè)備選,但是這個(gè)模型更多的是在并發(fā)對(duì)象領(lǐng)域的語(yǔ)義,而不是通常意義上的計(jì)算模型)。它有點(diǎn)類似于“***實(shí)踐”,在不同的計(jì)算模型上有著完全不同實(shí)現(xiàn)方式和含義。因此對(duì)比對(duì)象技術(shù)和其他技術(shù)的時(shí)候,搞清楚到底是哪一種面向?qū)ο缶妥兊酶裢庵匾饋?lái)。
兩種不同的面向?qū)ο?/strong>
目前流行的對(duì)象技術(shù),實(shí)際上有兩個(gè)截然不同的源頭。它們分別在兩個(gè)完全不同的計(jì)算模型上發(fā)展起來(lái),但是都頂著“面向?qū)ο?rdquo;這個(gè)帽子。
***種對(duì)象技術(shù)出現(xiàn)的較晚,在1979年以后。它是以抽象數(shù)據(jù)類型(ADT,Abstract Data Type)為源起,發(fā)展出來(lái)的面向?qū)ο蠹夹g(shù)。也就是首先被C++所采用的面向?qū)ο蠹夹g(shù)。
C++作為“更好的C”,繼承了C語(yǔ)言對(duì)于程序的看法,也就是數(shù)據(jù)抽象(Data Abstraction)和過(guò)程。面向?qū)ο蠹夹g(shù)在C++中,是作為一種更好的數(shù)據(jù)抽象的方式而存在的。
數(shù)據(jù)抽象在這類面向?qū)ο笳Z(yǔ)言中是一種關(guān)鍵的抽象方式。所謂數(shù)據(jù)抽象,在計(jì)算機(jī)發(fā)展的早期是一種非常關(guān)鍵的技術(shù)。眾所周知,計(jì)算機(jī)在裝置模型上是一個(gè)存儲(chǔ)和一組指令集,而二進(jìn)制的存儲(chǔ)實(shí)際上是沒(méi)有任何類型表示的。整數(shù),浮點(diǎn)這些操作必須通過(guò)相應(yīng)的約定,再以指令集的形式進(jìn)行支持。而隨著計(jì)算機(jī)的發(fā)展,簡(jiǎn)單的數(shù)據(jù)類型顯然已經(jīng)不能滿足應(yīng)用的需要。這時(shí)候一種靈活且有效的類型系統(tǒng),就成了一種自然的追求(直到80年代初,類型系統(tǒng)都是計(jì)算機(jī)科學(xué)研究的重要方向之一)。
在C++中(以及后來(lái)的Java和C#),對(duì)象是一種構(gòu)造數(shù)據(jù)類型的方式,把每個(gè)“類”看作一段存儲(chǔ)(狀態(tài))和操作(方法)的集合。“類”作為已經(jīng)存在的類型系統(tǒng)的一種擴(kuò)展(這一點(diǎn)在C++中體現(xiàn)得尤其強(qiáng)烈)。在這類語(yǔ)言種,“類”(class)實(shí)際上代替了“對(duì)象”(object)成為了頭等公民。構(gòu)造一個(gè)更好的類型系統(tǒng),是這種面向?qū)ο蠹夹g(shù)所要解決的問(wèn)題。與其說(shuō)是面向?qū)ο?,不如說(shuō)是面向類或面向類型的。
從計(jì)算語(yǔ)義上說(shuō),這類對(duì)象技術(shù)仍然是裝置的操作語(yǔ)義,和面向過(guò)程的沒(méi)有實(shí)質(zhì)上的區(qū)別。唯一的不同是,被這種對(duì)象語(yǔ)言操作的機(jī)器,可以借由對(duì)象技術(shù)擴(kuò)展機(jī)器所支持的類型。這種面向?qū)ο蠹夹g(shù)是過(guò)程技術(shù)的一種發(fā)展,雖然在抽象層次上沒(méi)有什么太大的提高,但在實(shí)踐上已經(jīng)是巨大的進(jìn)步。
另一種對(duì)象技術(shù)出現(xiàn)的很早,大概在60年代末就出現(xiàn)了,直到80年代初還有發(fā)展。但是很長(zhǎng)一段時(shí)間內(nèi)并不是太主流的做法,反而并不太為人所知。
在函數(shù)式語(yǔ)言里,因?yàn)楦唠A函數(shù)(High Order Function)的存在,數(shù)據(jù)可由函數(shù)來(lái)表達(dá)。這就是函數(shù)語(yǔ)言里一個(gè)非常重要的觀點(diǎn):Data as Procedure。在函數(shù)語(yǔ)言中,可以構(gòu)造一種非常類似于對(duì)象的高階函數(shù):
- (define (make-user name age sex)
- (define (dispatch message)
- (cond ((eq? message 'getName) name)
- ((eq? message 'getAge) age)
- ((eq? message 'getSex) sex))
- (else (error 'messageNotUnderstand))))dispatch)
- (define vincent (make-user 'Vincent 30 'Male))
- (vincent 'getName)
如上面的Lisp代碼所示,可以借由返回一個(gè)dispatch函數(shù),將基本數(shù)據(jù)組合成一個(gè)更復(fù)雜的數(shù)據(jù)對(duì)象,而通過(guò)高階函數(shù)的后續(xù)調(diào)用,可以使用相應(yīng)的選擇器(selector)與數(shù)據(jù)對(duì)象交互。這種風(fēng)格的數(shù)據(jù)抽象被稱作“消息傳遞”(Message Passing),是早期面向?qū)ο蠹夹g(shù)的雛形,無(wú)論是Smalltalk還是CLOS都是以這種技術(shù)為藍(lán)本,設(shè)計(jì)的對(duì)象系統(tǒng),包括后來(lái)的Ruby,實(shí)際上也是這種模型的一個(gè)發(fā)展。
因此實(shí)際上,就算在函數(shù)式語(yǔ)言上面,我們?nèi)匀豢梢酝ㄟ^(guò)引入這種對(duì)象的形式,對(duì)函數(shù)進(jìn)行相應(yīng)的模塊化和局部化。這種形式的對(duì)象與函數(shù)本身沒(méi)有任何差別,因此這種類型的對(duì)象系統(tǒng),被稱作“方便的接口”,用于簡(jiǎn)化對(duì)象的函數(shù)的訪問(wèn)和調(diào)用。
在函數(shù)式語(yǔ)言里,另一個(gè)非常重要的概念就是“副作用”(Side effect,即函數(shù)可以修改某個(gè)存在的狀態(tài))。像Lisp并不是純函數(shù)語(yǔ)言,因此是允許狀態(tài)修改的。因此對(duì)象技術(shù)除了可以被看作函數(shù)局部化和模塊化的方法之外,還可以看作副作用局部化的一種方式。采用這類面向?qū)ο蠹夹g(shù)的語(yǔ)言,通常被稱作動(dòng)態(tài)面向?qū)ο笳Z(yǔ)言。
這類對(duì)象語(yǔ)言通常都會(huì)保持一些函數(shù)式語(yǔ)言的特性,比如lambda的各種變體,比如較容易的函數(shù)組合,比如curry,比如高階函數(shù)。而且由于這類對(duì)象系統(tǒng)是從函數(shù)式發(fā)展出來(lái)的,也更加推崇一些副作用小的,利用高階函數(shù)的對(duì)象設(shè)計(jì)方法。比如,不變體(Immutable object)回調(diào)等等。
計(jì)算語(yǔ)義上,無(wú)副作用的對(duì)象系統(tǒng)實(shí)際上和Lambda演算享有同樣的計(jì)算語(yǔ)義。而帶副作用的本身只能被看作一種壞的實(shí)現(xiàn),在函數(shù)上都沒(méi)有明確語(yǔ)義。僅僅能夠看作對(duì)于副作用的局部化和模塊化。
以上,我們簡(jiǎn)單地看了一下兩種不同的“面向?qū)ο?rdquo;技術(shù)。其中一種是用來(lái)解決如何構(gòu)造更好的類型系統(tǒng)的,另一種是用來(lái)對(duì)函數(shù)和副作用進(jìn)行有效模塊化和局部化的。如果單以這兩種面向?qū)ο蠹夹g(shù)和函數(shù)式語(yǔ)言去比較,實(shí)在不是一個(gè)層次的東西。那么為什么我們最近能夠聽(tīng)到這么多函數(shù)和對(duì)象的討論呢?
新的發(fā)展
靜態(tài)類型函數(shù)語(yǔ)言
最早的函數(shù)語(yǔ)言是不太在意類型的,因?yàn)橛蠨ata as Procedure的存在,lambda演算可以通過(guò)把參數(shù)類型抽象成另一個(gè)高階函數(shù)來(lái)繞過(guò)函數(shù)參數(shù)類型問(wèn)題(把參數(shù)也變成lambda,每個(gè)函數(shù)都看作參數(shù)和函數(shù)體的高階)。然而隨著形式化類型系統(tǒng)在理論上的發(fā)展,把lambda演算擴(kuò)展為typed lambda演算自然就是一種很自然的推論。
隨著在此基礎(chǔ)上發(fā)展出來(lái)的ML族和Haskell語(yǔ)言的日漸成熟,以及代數(shù)數(shù)據(jù)類型(algebraic data type)的引入,這些語(yǔ)言可以較為容易地構(gòu)造出非常復(fù)雜的類型系統(tǒng)。而且伴隨著類型推演和類型計(jì)算的引入,類型間復(fù)雜的關(guān)系也可以較為容易表達(dá)。由此,靜態(tài)類型函數(shù)式語(yǔ)言也開(kāi)始挑戰(zhàn)以對(duì)象為基礎(chǔ)的類型系統(tǒng)構(gòu)造方法。
實(shí)際上這里函數(shù)語(yǔ)言的挑戰(zhàn)是類型系統(tǒng)之爭(zhēng),而非面向?qū)ο蠛秃瘮?shù)語(yǔ)言之爭(zhēng)。因此,消息傳遞類的對(duì)象語(yǔ)言根本不在討論之列,而對(duì)于靜態(tài)類型面向?qū)ο笳Z(yǔ)言而言,除了C++外(而對(duì)于C++,面向?qū)ο髢H僅是構(gòu)造類型系統(tǒng)的一種方式,另一種則是著名的范型編程。我仍然相信,在語(yǔ)義上靜態(tài)類型函數(shù)語(yǔ)言會(huì)勝過(guò)C++很多,但是彈性和表現(xiàn)力C++并不會(huì)差太多),其他主流語(yǔ)言如Java和C#,類型系統(tǒng)的已經(jīng)被限制在一個(gè)相對(duì)簡(jiǎn)單的范疇內(nèi),說(shuō)完敗也不為過(guò)。
主流平臺(tái)也為需要處理復(fù)雜類型系統(tǒng)的開(kāi)發(fā)者提供了不同的選擇,比如.NET平臺(tái)上的F#。以及JVM上的Scala。都是在主流平臺(tái)上引入靜態(tài)類型函數(shù)語(yǔ)言的一些特征,來(lái)簡(jiǎn)化復(fù)雜類型系統(tǒng)的構(gòu)造。
并發(fā)編程/并行計(jì)算/多核編程
Lisp并不是一個(gè)純函數(shù)語(yǔ)言,它允許有副作用存在。后來(lái)發(fā)展了一些嚴(yán)格的純函數(shù)語(yǔ)言,嚴(yán)格禁止副作用。也就是所有變量都和數(shù)學(xué)中的變量具有相同的語(yǔ)義,不能修改。然而計(jì)算機(jī)程序終歸是要處理狀態(tài)變化、輸入輸出這些不具有函數(shù)語(yǔ)義的操作的。一些純函數(shù)語(yǔ)言開(kāi)始引入了更精巧的方式來(lái)管理狀態(tài),比如Monad。Monad的傳遞性使得副作用的擴(kuò)散在函數(shù)中變得更明確可見(jiàn)。
這種方式本來(lái)是用來(lái)解決純函數(shù)語(yǔ)言內(nèi)副作用處理的一種技巧,但是恰好趕上Intel受制于生產(chǎn)技術(shù),無(wú)法再通過(guò)提高單核頻率以追趕摩爾定律,必須通過(guò)集成多核的方式來(lái)制造更快的CPU。多核CPU作為一種新的事物,給計(jì)算機(jī)界帶來(lái)了新的恐慌,大家覺(jué)得有必要使用一種新的編程模型以充分利用多核的優(yōu)勢(shì)。
而***個(gè)嘗試的方案就是將計(jì)算分布到多個(gè)CPU上,也就是利用多核進(jìn)行并行計(jì)算。這時(shí)候,純函數(shù)式語(yǔ)言對(duì)于副作用的處理,恰好給多核編譯器提供了一個(gè)理想的優(yōu)化方式:即所有無(wú)作用的函數(shù)皆可以隨意分布到多核上,而帶副作用的函數(shù)則無(wú)法分布。通過(guò)對(duì)于類型系統(tǒng)的簡(jiǎn)單識(shí)別和標(biāo)注,就可以自動(dòng)地將純函數(shù)式程序編譯為支持多核的程序。這在一段時(shí)間內(nèi),形成一種函數(shù)式語(yǔ)言是自動(dòng)適應(yīng)多核的,而面向?qū)ο蟪绦騽t需要重寫的印象。一時(shí)間內(nèi),函數(shù)與對(duì)象之間的選擇實(shí)際上變成了多核和單核的選擇。
好在還有Amdahl's law存在,事實(shí)也證明除去一些特定的應(yīng)用場(chǎng)景,自動(dòng)編譯為支持多核并行的函數(shù)式程序并不快多少,而轉(zhuǎn)化為純函數(shù)程序的成本卻高出不少,同時(shí)大多數(shù)純函數(shù)語(yǔ)言都帶有學(xué)術(shù)性質(zhì),對(duì)于團(tuán)隊(duì)開(kāi)發(fā)并不友好。在加上JVM和.NET CLR對(duì)于多核都做出了一些回應(yīng)。因此除去一些計(jì)算密集型應(yīng)用,純函數(shù)語(yǔ)言并沒(méi)比面向?qū)ο蠛枚嗌佟?/p>
峰回路轉(zhuǎn)的是,由消息傳遞風(fēng)格發(fā)展出來(lái)的actor模型,利用操作系統(tǒng)的進(jìn)程/線程特性,在一個(gè)合理的粒度上很好地利用了多核的能力,簡(jiǎn)化了并發(fā)編程。雖然***個(gè)著名的實(shí)現(xiàn)是Erlang的actor系統(tǒng),但是由于消息傳遞風(fēng)格和面向?qū)ο竽P拖嗳ゲ贿h(yuǎn),很快就在各種面向?qū)ο笳Z(yǔ)言中有了類庫(kù)支持。雖然利用當(dāng)代函數(shù)語(yǔ)言的語(yǔ)法特性,actor可以實(shí)現(xiàn)得更簡(jiǎn)潔,但是對(duì)象對(duì)于副作用和狀態(tài)的封裝,更好地解決了在并發(fā)環(huán)境下對(duì)于共享狀態(tài)的操作,反而有了更好的發(fā)展。
以上,我們看了函數(shù)式語(yǔ)言中兩個(gè)新的發(fā)展,以及圍繞這些發(fā)展涉及的一些“對(duì)象v.s.函數(shù)”的討論。正如本文一開(kāi)始所說(shuō),對(duì)象技術(shù)作為一種工程實(shí)踐,其發(fā)展總是依托于其他更基本的計(jì)算模型的演化的。函數(shù)語(yǔ)言的發(fā)展,使得我們對(duì)于對(duì)象的認(rèn)識(shí)和理解有了更深更好的認(rèn)識(shí)。而對(duì)象作為函數(shù)的“方便的接口”總會(huì)在新的發(fā)展中,讓我們更加便利的享有函數(shù)式和其他計(jì)算模型發(fā)展的成果。
回到本文最開(kāi)始的討論,函數(shù)的發(fā)展會(huì)的確會(huì)促使一些對(duì)象技術(shù)的消亡,但也會(huì)產(chǎn)生新的對(duì)象技術(shù)?;蛟S更好的理解和掌握函數(shù),類型系統(tǒng)才是真正掌握對(duì)象技術(shù)的捷徑,也未可知。
本文通過(guò)對(duì)對(duì)象技術(shù)和函數(shù)技術(shù)的介紹,闡述了作者的觀點(diǎn),其實(shí)并不存在,誰(shuí)會(huì)取代誰(shuí)的說(shuō)法,這只是如何看待面向?qū)ο蠹夹g(shù)的問(wèn)題。希望對(duì)你會(huì)有幫助。
【編輯推薦】