3.1 語言的設(shè)計

書名:《代碼的未來》
作者:松本行弘(Yukihiro Matsumoto)
譯者:**周自恒 **


本試讀章節(jié)摘自:『第3章 編程語言的新潮流』

接下來,我們從語言設(shè)計的角度,來比較一下 Java、JavaScript、Ruby 和 Go 這 4 種語言。這幾種語言看起來彼此完全不同,但如果選擇一個合適的標準,就可以將它們非常清楚地進行分類,如圖所示。

圖:4 種語言的分類
圖:4 種語言的分類

JavaScript 是客戶端語言的代表,Java 其實也在其黎明期作為客戶端語言活躍過一段時間,應(yīng)該有很多人還記得 Java Applet 這個名詞。之后,Java 轉(zhuǎn)型為服務(wù)器端語言的代表,地位也扶搖直上,但考慮到它的出身,這里還是將其分類為客戶端語言。

另一個分類標準,就是靜態(tài)和動態(tài)。所謂靜態(tài),就是不實際運行程序,僅通過程序代碼的字面來確定結(jié)果的意思;而所謂動態(tài),就是只有當運行時才確定結(jié)果的意思。靜態(tài)、動態(tài)具體所指的內(nèi)容有很多種,大體上來分的話就是運行模式和類型。這 4 種語言全都具備面向?qū)ο蟮男再|(zhì),而面向?qū)ο蟊旧砭褪且环N包含動態(tài)概念的性質(zhì)。不過,在這幾種語言之中,Java 和 Go 是比較偏重靜態(tài)一側(cè)的語言,而 Ruby 和 JavaScript 則是比較偏重動態(tài)一側(cè)的語言。

客戶端與服務(wù)器端

首先,我們先將這些語言按照客戶端和服務(wù)器端來進行分類。如前面所說,這種分類是以該語言剛剛出現(xiàn)時所使用的方式為基準的。

現(xiàn)在 Java 更多地被用作服務(wù)器端語言,而我們卻將它分類到客戶端語言中,很多人可能感到有點莫名其妙。Java 確實現(xiàn)在已經(jīng)很少被用作客戶端語言了,但是我們不能忘記,誕生于 1995 年的 Java,正是伴隨嵌入在瀏覽器中的 Applet 技術(shù)而出現(xiàn)的。

Java 將虛擬機(VM)作為插件集成到瀏覽器中,將編譯后的 Java 程序(Applet)在虛擬機上運行,這種技術(shù)當初是為了增強瀏覽器的功能。再往前追溯的話,Java 原本名叫 Oak,是作為面向嵌入式設(shè)備的編程語言而誕生的。因此,從出身來看的話,Java 還是一種面向客戶端的編程語言。

Java 所具備的 VM 和平臺無關(guān)性字節(jié)碼等特性,本來就是以在客戶端運行 Applet 為目的的。在各種不同的環(huán)境下都能夠產(chǎn)生相同的行為,這樣的特性對于服務(wù)器端來說雖然也不能說是毫無價值,但是服務(wù)器環(huán)境是可以由服務(wù)提供者來自由支配的,因此至少可以說,這樣的特性無法帶來關(guān)鍵性的好處吧。另一方面,在客戶端環(huán)境中,操作系統(tǒng)和瀏覽器都是千差萬別,因此對平臺無關(guān)性的要求一直很高。

Java 誕生于互聯(lián)網(wǎng)的黎明時期,那個時候瀏覽器還不是電腦上必備的軟件。當時主流的瀏覽器有 Mosaic 和 Netscape Navigator 等,除此之外還有一些其他類似的軟件,而 Internet Explorer 也是剛剛才嶄露頭角。

在那個充滿夢想的時代,如果能開發(fā)出一種功能上有亮點的瀏覽器就有可能稱霸業(yè)界。原 Sun Microsystems 公司曾推出了一個用 Java 編寫的瀏覽器 HotJava,向世界展示了 Applet 的可能性。然而,隨著瀏覽器市場格局的逐步固定,他們轉(zhuǎn)變了策略,改為向主流瀏覽器提供插件來集成 Java,從而對 Applet 的運行提供支持。

向服務(wù)器端華麗轉(zhuǎn)身

然而,Java 自誕生之后,并未在客戶端方面取得多大的成功,于是便開始著手進入服務(wù)器端領(lǐng)域。造成這種局面有很多原因,我認為其中最主要的原因應(yīng)該是在 Applet 這個平臺上遲遲沒有出現(xiàn)一款殺手級應(yīng)用(killer app)。

處于剛剛誕生之際的 Java 遭到了很多批判,如體積臃腫、運行緩慢等,不同瀏覽器上的 Java 插件之間也存在一些兼容性方面的問題,使得 Applet 應(yīng)用并沒有真正流行起來。在這個過程中,JavaScript 作為客戶端編程語言則更加實用,并獲得了越來越多的關(guān)注。當然,在那個時候 Java 已經(jīng)完全確立了自己作為服務(wù)器端編程語言的地位,因此喪失客戶端這塊領(lǐng)地也不至于感到特別肉痛。

Java 從客戶端向服務(wù)器端的轉(zhuǎn)身可以說是相當成功的。與此同時,Sun Microsystems 和 IBM 等公司著手對 JVM(Java VM)進行改良,使得其性能得到了改善,在某些情況下性能甚至超越了 C++。想想之前對 Java 性能惡評如潮的情形,現(xiàn)在 Java 能有這樣的性能和人氣簡直就像做夢一樣。

在服務(wù)器端獲得成功的四大理由

由于我本人沒有大規(guī)模實踐過 Java 編程,因此對于 Java 在服務(wù)器端取得成功的來龍去脈,說真的并不是很了解。不過,如果讓我想象一下的話,大概有下面幾個主要的因素。

1. 可移植性

雖然服務(wù)器環(huán)境比客戶端環(huán)境更加可控,但服務(wù)器環(huán)境中所使用的系統(tǒng)平臺種類也相當多,如 Linux、Solaris、FreeBSD、Windows 等,根據(jù)需要,可能還會在系統(tǒng)上線之后更換系統(tǒng)平臺。在這樣的情況下,Java 所具備的 “一次編寫,到處運行” 特性就顯得魅力十足了。

2. 功能強大

Java 在服務(wù)器端嶄露頭角是在 20 世紀 90 年代末,那個時候的狀況對 Java 比較有利。和 Java 在定位上比較相似的語言,即靜態(tài)類型、編譯型、面向?qū)ο蟮木幊陶Z言,屬于主流的也就只有 C++ 而已了。

在 Java 誕生的 20 世紀 90 年代中期,正好是我作為 C++ 程序員開發(fā) CAD 相關(guān)系統(tǒng)的時候。但當時 C++ 也還處于發(fā)展過程中,在實際的開發(fā)中,模板、異常等功能還無法真正得到運用。

相比之下,Java 從一開始就具備了垃圾回收(GC)機制,并在語言中內(nèi)置了異常處理,其標準庫也是完全運用了異常處理來設(shè)計的,這對程序員來說簡直是天堂。毫無疑問,Java 語言 的這些優(yōu)秀特性,是幫助其確立服務(wù)器端編程語言地位的功臣之一。

3. 高性能

Java 為了實現(xiàn)其 “一次編寫,到處運行” 的宣傳口號,并不是將程序直接轉(zhuǎn)換為系統(tǒng)平臺所對應(yīng)的機器語言,而是轉(zhuǎn)換為虛擬 CPU 的機器語言 “字節(jié)碼” (Bytecode),并通過搭載虛擬 CPU 的模擬器 JVM 來運行。JVM 歸根到底其實是在運行時用來解釋字節(jié)碼的解釋器,理論上說運行速度應(yīng)該無法與直接生成機器語言的原生編譯器相媲美。

事實上,在 Java 誕生初期,確實沒有達到編譯型語言應(yīng)有的運行速度,當時的用戶經(jīng)常抱怨 Java 太慢了,這樣的惡評令人印象深刻。

然而,技術(shù)的革新是偉大的。隨著各種技術(shù)的進步,現(xiàn)在 Java 的性能已經(jīng)能夠堪稱頂級。

例如,有一種叫做 JIT(Just In Time)編譯的技術(shù),可以在運行時將字節(jié)碼轉(zhuǎn)換成機器語言,經(jīng)過轉(zhuǎn)換之后就可以獲得和原生編譯一樣快的運行速度。在運行時進行編譯,就意味著編譯時間也會包含在運行時間里面。因此,優(yōu)秀的 JIT 編譯器會通過偵測運行信息,僅將需要頻繁運行的瓶頸部分進行編譯,從而大大削減編譯所需的時間。而且,利用運行時編譯,可以不用考慮連接的問題而積極運用內(nèi)聯(lián)擴展,因此在某些情況下,運行速度甚至可以超過 C++。

在 Java 中,其性能提高的另一個障礙就是 GC。GC 需要對對象進行掃描,將不用的對象進行回收,這個過程和程序本身要進行的操作是無關(guān)的,換句話說,就是做無用功,因此而消耗的時間拖累了 Java 程序的性能。作為對策,在最新的 JVM 中,采用了并行回收、分代回收等技術(shù)。

4. 豐富的庫

隨著 Java 的人氣直升,應(yīng)用逐漸廣泛,Java 能夠使用的庫也越來越多。庫的增加提高了開發(fā)效率,從而又反過來拉高了 Java 的人氣,形成了一個良性循環(huán)。現(xiàn)在 Java 的人氣已經(jīng)無可撼動了。

客戶端的 JavaScript

Applet 在客戶端對擴展瀏覽器功能做出了嘗試,然而它并不太成功。在瀏覽器畫面中的一個矩形區(qū)域中運行應(yīng)用程序的 Applet,并沒有作為應(yīng)用程序的發(fā)布手段而流行起來。

幾乎是在同一時期出現(xiàn)的 JavaScript,也是一種集成在瀏覽器中的語言,但是它可以在一般的網(wǎng)頁中嵌入程序邏輯,這一點是和 Java Applet 完全不同的方式,卻最終獲得了成功。

JavaScript 是由原 Netscape Communications 公司開發(fā)的,通過 JavaScript,用戶點擊網(wǎng)頁上的鏈接和按鈕時,不光可以進行頁面的跳轉(zhuǎn),還可以改寫頁面的內(nèi)容。這樣的功能十分便利,因此 Netscape Navigator 之外的很多瀏覽器都集成了 JavaScript。

隨著瀏覽器的不斷競爭和淘汰,當主流瀏覽器全部支持 JavaScript 時,情況便發(fā)生了變化。像 Google 地圖這樣的產(chǎn)品,整體的框架是由 HTML 組成的,但實際顯示的部分卻是通過 JavaScript 來從服務(wù)器獲取數(shù)據(jù)并顯示出來,這樣的手法從此開始流行起來。

在 JavaScript 中與服務(wù)器進行異步通信的 API 叫做 XMLHttpRequest,因此從它所衍生出的手法便被稱為 Ajax(Asynchronous JavaScript and XML,異步 JavaScript 與 XML)。在美國有一種叫做 Ajax 的廚房清潔劑,說不定是從那個名字模仿而來的。

性能顯著提升

目前,客戶端編程語言中 JavaScript 已成為一個強有力的競爭者,伴隨著 JavaScript 重要性的不斷提高,對 JavaScript 引擎的投資也不斷增加,使 JavaScript 的性能得到了顯著改善。改善 JavaScript 性能的主要技術(shù),除了和 Java 相同的 JIT 和 GC 之外,還有特殊化(Specialization)技術(shù)。

與 Java 不同,JavaScript 是一種動態(tài)語言,不帶有變量和表達式的類型信息,針對類型進行優(yōu)化是非常困難的,因此性能和靜態(tài)語言相比有著先天的劣勢,而特殊化就是提高動態(tài)語言性能的技術(shù)之一。

JavaScript 函數(shù):

function fact(n) {
if (n == 1) return 1;
return n * fact(n-1);
}

我們設(shè)想如上所示的這樣一個 JavaScript 函數(shù)。這個函數(shù)是用于階乘計算的,大多數(shù)情況下,其參數(shù) n 應(yīng)該都是整數(shù)。由于 JIT 需要統(tǒng)計運行時信息,因此 JavaScript 解釋器也知道參數(shù) n 大多數(shù)情況下是整數(shù)。

于是,當解釋器對 fact 函數(shù)進行 JIT 編譯時,會生成兩個版本的函數(shù):一個是 n 為任意對象的通用版本,另一個是假設(shè) n 為整數(shù)的高速版本。當參數(shù) n 為整數(shù)時(即大多數(shù)情況下),就會運行那個高速版本的函數(shù),便實現(xiàn)了與靜態(tài)語言幾乎相同的運行性能。

除此之外,最新的 JavaScript 引擎中還進行了其他大量的優(yōu)化,說 JavaScript 是目前最快的動態(tài)語言應(yīng)該并不為過。

JavaScript 在客戶端稱霸之后,又開始準備向服務(wù)器端進軍了。JavaScript 的存在感在將來應(yīng)該會越來越強吧。

服務(wù)器端的 Ruby

客戶端編程的最大問題,就是必須要求每一臺客戶端都安裝相應(yīng)的軟件環(huán)境。在 Java 和 JavaScript 誕生的 20 世紀 90 年代后半,互聯(lián)網(wǎng)用戶還只局限于一部分先進的用戶,然而現(xiàn)在互聯(lián)網(wǎng)已經(jīng)大大普及,用戶的水平構(gòu)成也跟著變得復(fù)雜起來,讓每一臺客戶端都安裝相應(yīng)的軟件環(huán)境,就會大大提高軟件部署的門檻。

而相對的,在服務(wù)器端就沒有這樣的制約,可以選擇最適合自己的編程語言。

在 Ruby 誕生的 1993 年,互聯(lián)網(wǎng)還沒有現(xiàn)在這樣普及,因此 Ruby 也不是一開始就面向 Web 服務(wù)器端來設(shè)計的。然而,從 WWW 黎明期開始,為了實現(xiàn)動態(tài)頁面而出現(xiàn)了通用網(wǎng)關(guān)接口(Common Gateway Interface,CGI)技術(shù),而 Ruby 則逐漸在這種技術(shù)中得到了應(yīng)用。

所謂 CGI,是通過 Web 服務(wù)器的標準輸入輸出與程序進行交互,從而生成動態(tài) HTML 頁面 的接口。只要可以對標準輸入輸出進行操作,那么無論任何語言都可以編寫 CGI 程序,這不得不歸功于 WWW 設(shè)計的靈活性,使得動態(tài)頁面可以很容易地編寫出來,也正是因為如此,使得 WWW 逐漸風靡全世界。

在 WWW 中,來自 Web 服務(wù)器的請求信息是以文本的方式傳遞的,反過來,返回給 Web 服務(wù)器的響應(yīng)信息也是以文本(HTML)方式傳遞的,因此擅長文本處理的編程語言就具有得天獨厚的優(yōu)勢。于是,腳本語言的時代到來了。以往只是用于文本處理的腳本語言,其應(yīng)用范圍便一下子擴大了。

早期應(yīng)用 CGI 的 Web 頁面大多是用 Perl 來編寫的,而作為 “Better Perl” 的 Ruby 也隨之逐步得到越來越多的應(yīng)用。

Ruby on Rails 帶來的飛躍

2004 年,隨著 Ruby on Rails 的出現(xiàn),使得 Web 應(yīng)用程序的開發(fā)效率大幅提升,也引發(fā)了廣泛的關(guān)注。當時,已經(jīng)出現(xiàn)了很多 Web 應(yīng)用程序框架,而 Ruby on Rails 可以說是后發(fā)制人的。 Ruby on Rails 的特性包括:

  • ?完全的 MVC 架構(gòu)?
  • 不使用配置文件(尤其是 XML)?
  • 堅持簡潔的表達
  • 積極運用元編程
  • 對 Ruby 核心的大膽擴展

基于這些特性,Ruby on Rails 實現(xiàn)了很高的開發(fā)效率和靈活性,得到了廣泛的應(yīng)用??梢哉f,Ruby 能擁有現(xiàn)在的人氣,基本上都是 Ruby on Rails 所作出的貢獻。

目前,作為服務(wù)器端編程語言,Ruby 的人氣可謂無可撼動。有一種說法稱,以硅谷為中心的 Web 系創(chuàng)業(yè)公司中,超過一半都采用了 Ruby。

但這也并不是說,只要是服務(wù)器端環(huán)境,Ruby 就一定可以所向披靡。在規(guī)模較大的企業(yè)中,向網(wǎng)站運營部門管理的服務(wù)器群安裝軟件也并不容易。實際上,在某個大企業(yè)中,曾經(jīng)用 Ruby on Rails 開發(fā)了一個面向技術(shù)人員的 SNS,只用很短的時間就完成搭建了,但是等到要正式上線的時候,運營部門就會以 “這種不知道哪個的家伙開發(fā)的,也沒經(jīng)過第三方安全認證的 Ruby 解釋器之類的軟件,不可以安裝在我們數(shù)據(jù)中心的主機上面” 這樣的理由來拒絕安裝,這真是相當頭疼。

不過,開發(fā)部門的工程師們并沒有氣餒,而是用 Java 編寫的 Ruby 解釋器 JRuby,將開發(fā)好的 SNS 轉(zhuǎn)換為 jar 文件,從而使其可以在原 Sun Microsystems 公司的應(yīng)用程序服務(wù)器 GlassFish 上運行。當然,JVM 和 GlassFish 都已經(jīng)在服務(wù)器上安裝好了,這樣一來運營方面也就沒有理由拒絕了。多虧了 JRuby,結(jié)局皆大歡喜。

JRuby 還真是在關(guān)鍵時刻大顯身手呢。

服務(wù)器端的 Go

Go 是一種新興的編程語言,但它出身名門,是由著名 UNIX 開發(fā)者羅勃 · 派克和肯 · 湯普遜開發(fā)的,因此受到了廣泛的關(guān)注。

Go 的誕生背景源于 Google 公司中關(guān)于編程語言的一些問題。在 Google 公司中,作為優(yōu)化編程環(huán)境的一環(huán),在公司產(chǎn)品開發(fā)中所使用的編程語言,僅限于 C/C++、Java、Python 和JavaScript。實際上也有人私底下在用 Ruby,不過正式產(chǎn)品中所使用的語言僅限上述 4 種。

這 4 種語言在使用上遵循著一定的分工:客戶端語言用 JavaScript,服務(wù)器端語言用腳本系的 Python,追求大規(guī)?;蚋咝阅軙r用 Java,文件系統(tǒng)等面向平臺的系統(tǒng)編程用 C/C++。在這些語言中,Google 公司最不滿意的就是 C/C++ 了。

和其他一些編程語言相比,C/C++ 的歷史比較久,因此不具備像垃圾回收等最近的語言所提供的編程輔助功能。因此,由于開發(fā)效率一直無法得到提高,便產(chǎn)生了設(shè)計一種 “更好的” 系統(tǒng)編程語言的需求。而能夠勝任這一位置的,正是全新設(shè)計的編程語言 Go。

Go 具有很多特性,(從我的觀點來看)比較重要的有下列幾點:

  • ?垃圾回收
  • 支持并行處理的 Goroutine
  • Structural Subtyping(結(jié)構(gòu)子類型)

關(guān)于最后一點 Structural Subtyping,我們會在后面對類型系統(tǒng)的講解中進行說明。

靜態(tài)與動態(tài)

剛才我們已經(jīng)將這 4 種語言,從客戶端、服務(wù)器端的角度進行了分類。接下來我們再從動態(tài)、靜態(tài)的角度來看一看這幾種語言。

正如剛才所講過的,所謂靜態(tài),就是無需實際運行,僅根據(jù)程序代碼就能確定結(jié)果的意思;而所謂動態(tài),則是只有到了運行時才能確定結(jié)果的意思。

不過,無論任何程序,或多或少都包含了動態(tài)的特性。如果一個程序完全是靜態(tài)的話,那就意味著只需要對代碼進行字面上的分析,就可以得到所有的結(jié)果,這樣一來程序的運行就沒有任何意義了。例如,編程計算 6 的階乘,如果按照完全靜態(tài)的方式來編寫的話,應(yīng)該是下面這樣的:

puts "720"

不過,除非是個玩具一樣的演示程序,否則不會開發(fā)出這樣的程序來。在實際中,由于有了輸入的數(shù)據(jù),或者和用戶之間的交互,程序才能在每次運行時都能得到不同的要素。

因此,作為程序的實現(xiàn)者,編程語言也多多少少都具備動態(tài)的性質(zhì)。所謂動態(tài)還是靜態(tài),指的是這種語言對于動態(tài)的功能進行了多少限制,或者反過來說,對動態(tài)功能進行了多少積極的強化,我們所探討的其實是語言的這種設(shè)計方針。

例如,在這里所列舉的 4 種編程語言都是面向?qū)ο蟮恼Z言,而面向?qū)ο蟮恼Z言都會具備被稱為多態(tài)(Polymorphism)或者動態(tài)綁定的動態(tài)性質(zhì)。即,根據(jù)存放在變量中的對象的實際性質(zhì),自動選擇一種合適的處理方式(方法)。這樣的功能可以說是面向?qū)ο缶幊痰谋举|(zhì)。

屬于動態(tài)的編程語言,其動態(tài)的部分,主要是指運行模式和類型。這兩者是相互獨立的概念,但采用動態(tài)類型的語言,其運行模式也具有動態(tài)的傾向;反之也是一樣,在靜態(tài)語言中,運行模式在運行時的靈活性也會受到一定的限制。

動態(tài)運行模式

所謂動態(tài)運行模式,簡單來說,就是運行中的程序能夠識別自身,并對自身進行操作。對程序自身進行操作的編程,也被稱為元編程(Metaprogramming)。

在 Ruby 和 JavaScript 中,元編程是十分自然的,比如查詢某個對象擁有哪些方法,或者在運行時對類和方法進行定義等等,這些都是理所當然的事。

另一方面,在 Java 中,類似元編程的手法,是通過 “反射 API” 來實現(xiàn)的。雖然對類進行取出、操作等功能都是可以做到的,但并非像 Ruby 和 JavaScript 那樣讓人感到自由自在,而是 “雖然能做到,但一般也不會去用” 這樣的感覺吧。

Go 也是一樣。在 Go 中,通過利用 reflect 包可以獲取程序的運行時信息(主要是類型),但是(在我所理解的范圍內(nèi))無法實現(xiàn)進一步的元編程功能。而之所以沒有采用比 Java 更進一步的動態(tài)運行模式,恐怕是因為這(可能)在系統(tǒng)編程領(lǐng)域中必要性不大,或者是擔心對運行速度產(chǎn)生負面影響之類的原因吧。

何謂類型

從一般性的層面來看,類型2指的是對某個數(shù)據(jù)所具有的性質(zhì)所進行的描述。例如,它的結(jié)構(gòu)是怎樣的,它可以進行哪些操作,等等。動態(tài)類型的立場是數(shù)據(jù)擁有類型,且只有數(shù)據(jù)才擁有類型;而靜態(tài)類型的立場是數(shù)據(jù)擁有類型,而存放數(shù)據(jù)的變量、表達式也擁有類型,且類型是在編譯時就固定的。

然而,即便是靜態(tài)類型,由于面向?qū)ο笳Z言中的多態(tài)特性,也必須具備動態(tài)的性質(zhì),因此需要再追加一條規(guī)則,即實際的數(shù)據(jù)(類型),是靜態(tài)指定的類型的子類型。所謂子類型(Subtype),是指具有繼承關(guān)系,或者擁有同一接口,即靜態(tài)類型與數(shù)據(jù)的類型在系統(tǒng)上 “擁有同一性質(zhì)” 。

靜態(tài)類型的優(yōu)點

動態(tài)類型比較簡潔,且靈活性高,但靜態(tài)類型也有它的優(yōu)點。由于在編譯時就已經(jīng)確定了類型,因此比較容易發(fā)現(xiàn) bug。當然,程序中的 bug 大多數(shù)都是與邏輯有關(guān)的,而單純是類型錯誤而導致的 bug 只是少數(shù)派。不過,邏輯上的錯誤通常也伴隨著編譯時可以檢測到的類型不匹配,也就是說,通過類型錯誤可以讓其他的 bug 顯露出來。

除此之外,程序中對類型的描述,可以幫助對程序的閱讀和理解,或者可以成為關(guān)于程序行為的參考文檔,這可以說是一個很大的優(yōu)點。

此外,通過靜態(tài)類型,可以在編譯時獲得更多可以利用的調(diào)優(yōu)信息,編譯器便可以生成更優(yōu)質(zhì)的代碼,從而提高程序的性能。然而,通過 JIT 等技術(shù),動態(tài)語言也可以獲得與原生編譯 的語言相近的性能,這也說明,在今后靜態(tài)語言和動態(tài)語言之間的性能差距會繼續(xù)縮小。

動態(tài)類型的優(yōu)點

相對而言,動態(tài)類型的優(yōu)點,就在于其簡潔性和靈活性了。

說得極端一點的話,類型信息其實和程序運行的本質(zhì)是無關(guān)的。就拿階乘計算的程序來說,無論是用顯式聲明類型的 Java 來編寫,還是用非顯式聲明類型的 Ruby 來編寫, 其算法都是毫無區(qū)別的。然而,由于多了關(guān)于類型的描述,因此在 Java 版中,與算法本質(zhì)無關(guān)的代碼的分量也就增加了。

Java 編寫的階乘程序 :

class Sample {
    private static int fact(int n) {
        if (n == 1) return 1;
        return n * fact(n - 1);
    }
        public static void main(String[] argv) {
        System.out.println("6!="+fact(6));
    }
}

Ruby 編寫的階乘程序:

def fact(n)
    if n == 1
        1
    else
        n * fact(n - 1)
    end
end
print "6!=", fact(6), "\n"
---

而且,類型也帶來了更多的制約。如上所示的程序?qū)?6 的階乘進行了計算,但如果這個數(shù)字繼續(xù)增大,Java 版對超過 13 的數(shù)求階乘的話,就無法正確運行了。Java 的程序中,fact 方法所接受的參數(shù)類型顯式聲明為 int 型,而 Java 的 int 為 32 位,即可以表示到接近 20 億的整數(shù)。如果階乘的計算結(jié)果超出這個范圍,就會導致溢出。

當然,由于 Java 擁有豐富的庫資源,用 BigInteger 類就可以實現(xiàn)無上限的大整數(shù)計算,但這就需要對上面的程序做較大幅度的改動。而由于計算機存在 “int 的幅度為 32 位” 這一限制,就使得階乘計算的靈活性大大降低了。

另一方面,Ruby 版中則沒有這樣的制約,就算是計算 13 的階乘,甚至是 200 的階乘,都可以直接計算出來,而無需擔心如 int 的大小、計算機的限制等問題。

其實這里還是有點小把戲的。同樣是動態(tài)語言,用 JavaScript 來計算 200 的階乘就會輸出 Infinity(無窮大)。其實,JavaScript 的數(shù)值是浮點數(shù),因此無法像 Ruby 那樣支持大整數(shù)的計算。也就是說,要不受制約地進行計算,除了類型的性質(zhì)之外,庫的支持也是非常重要的。

有鴨子樣的就是鴨子

在動態(tài)語言中,一種叫做鴨子類型(Duck Typing)的風格被廣泛應(yīng)用。鴨子類型這個稱謂,據(jù)說是從下面這則英語童謠來的:

  • If it walks like a duck and quacks like a duck, it must be a duck.
    如果像鴨子一樣走路,像鴨子一樣呱呱叫,則它一定是一只鴨子

從這則童謠中,我們可以推導出一個規(guī)則,即如果某個對象的行為和鴨子一模一樣,那無論它真正的實體是什么,我們都可以將它看做是一只鴨子。也就是說,不考慮某個對象到底是哪一個類的實例,而只關(guān)心其擁有怎樣的行為(擁有哪些方法),這就是鴨子類型。因此,在程序中,必須排除由對象的類所產(chǎn)生的分支。

這是由 “編程達人” 大衛(wèi) · 托馬斯(Dave Thomas)所提出的。

例如,假設(shè)存在 log_puts(out, mesg)這樣一個方法,用來將 mesg 這個字符串輸出至 out 這個輸出目標中。out 需要指定一個類似 Ruby 中的 IO 對象,或者是 Java 中的 ReadStream 這樣的對象。在這里,本來是向文件輸出的日志,忽然想輸出到內(nèi)存的話,要怎么辦呢?比如說我想將日志的輸出結(jié)果合并成一個字符串,然后再將它取出。

在 Java 等靜態(tài)語言中,out 所指定的對象必須擁有共同的超類或者接口,而無法選擇一個完全無關(guān)的對象作為輸出目標。要實現(xiàn)這樣的操作,要么一開始就事先準備這樣一個接口,要么重寫原來的類,要么準備一個可以切換輸出目標的包裝對象(wrapper object)。無論如何,如果沒有事先預(yù)計到需要輸出到內(nèi)存的話,就需要對程序進行大幅的改動了。

如果是采用了鴨子類型風格的動態(tài)語言,就不容易產(chǎn)生這樣的問題。只要準備一個和 IO 對象具有同樣行為的對象,并將其指定為 out 的話,即便不對程序進行改動,log_puts 方法能夠成 功執(zhí)行的可能性也相當大。實際上,在 Ruby 中,確實存在和 IO 類毫無繼承關(guān)系,但和 IO 具有同樣行為的 StringIO 類,用來將輸出結(jié)果合并成字符串。

動態(tài)類型在編譯時所執(zhí)行的檢查較少,這是一個缺點,但與此同時,程序會變得更加簡潔,對于將來的擴展也具有更大的靈活性,這便是它的優(yōu)點。

Structural Subtyping

在 4 種語言中最年輕的 Go,雖然是一種靜態(tài)語言,但卻吸取了鴨子類型的優(yōu)點。Go 中沒有所謂的繼承關(guān)系,而某個類型可以具有和其他類型之間的可代換性,也就是說,某個類型的變量中是否可以賦予另一種類型的數(shù)據(jù),是由兩個類型是否擁有共同的方法所決定的。例如,對于 “A 型” 的變量,只要數(shù)據(jù)擁有 A 型所提供的所有方法,那么這個數(shù)據(jù)就可以賦值給該變量。像這樣,以類型的結(jié)構(gòu)來確定可代換性的類型關(guān)系,被稱為結(jié)構(gòu)子類型(Structural Subtyping);另一方面,像 Java 這樣根據(jù)聲明擁有繼承關(guān)系的類型具有可代換性的類型關(guān)系,被稱為名義子類型(Nominal Subtyping)。

在結(jié)構(gòu)子類型中,類型的聲明是必要的,但由于并不需要根據(jù)事先的聲明來確定類型之間的關(guān)系,因此就可以實現(xiàn)鴨子類型風格的編程。和完全動態(tài)類型的語言相比,雖然增加了對類型的描述,但卻可以同時獲得鴨子類型帶來的靈活性,以及靜態(tài)編譯所帶來了類型檢查這兩個優(yōu)點,可以說是一個相當劃算的交換。

小結(jié)

在這里,我們對 Ruby、JavaScript、Java、Go 這 4 種語言,從服務(wù)器端、客戶端,以及靜態(tài)、動態(tài)這兩個角度來進行了對比。這 4 種語言由于其不同的設(shè)計方針,而產(chǎn)生出了不同的設(shè)計風格,大家是否對此有了些許了解呢?

不僅僅是語言,其實很多設(shè)計都是權(quán)衡的結(jié)果。新需求、新環(huán)境,以及新范式,都催生出新的設(shè)計。而學習現(xiàn)有語言的設(shè)計及其權(quán)衡的過程,也可以為未來的新語言打下基礎(chǔ)。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評論 19 139
  • TITLE: 編程語言亂燉 碼農(nóng)最大的煩惱——編程語言太多。不是我不學習,這世界變化快! 有時候還是蠻懷念十幾、二...
    碼園老農(nóng)閱讀 5,590評論 2 35
  • 在日更文章一年后,我逐漸的意識到了一個問題,就是現(xiàn)在會寫文章了,但水平還是差得很。偶爾也會在簡書上上個首頁什么的,...
    超級賦能王張勝萍閱讀 690評論 12 8
  • 時間好快,15天的提煉總結(jié)班結(jié)束了,7篇文章的訓練,收獲頗豐。 首先,這是一場思想的盛宴。感謝教練選取的有思想深度...
    Leah82閱讀 246評論 0 1
  • 夏秋之交,踏野長堤;- 陽光明媚,碧空如洗;- 風自東南,徐若柔水;- 席地而坐,以聞秋香;- 百草豐茂,玉樹亭亭...
    一言一語一行閱讀 260評論 0 0

友情鏈接更多精彩內(nèi)容