標籤:自動 條件判斷 多次 接收器 結果 記錄 互連網 bre 程式
本文主要是介紹Go,從語言對比分析的角度切入。之所以選擇與Python、Erlang對比,是因為做為進階語言,它們語言特性上有較大的相似性,不過最主要的原因是這幾個我比較熟悉。
Go的很多語言特性借鑒與它的三個祖先:C,Pascal和CSP。Go的文法、資料類型、控制流程等繼承於C,Go的包、面對對象等思想來源於Pascal分支,而Go最大的語言特色,基於管道通訊的協程並行存取模型,則借鑒於CSP分支。
Go/Python/Erlang語言特性對比
如《程式設計語言與範式》一文所說,不管語言如何層出不窮,所有語言的設計離不開2個基本面:控制流程和資料類型。為了提升語言描述能力,語言一般都提供控制抽象和資料抽象。本小節的語言特性對比也從這4個維度入手,詳見(點擊見大圖)。
圖中我們可以看出,相比於Python的40個特性,Go只有31個,可以說Go在語言設計上是相當克制的。比如,它沒有隱式的數值轉換,沒有建構函式和解構函式,沒有運算子多載,沒有預設參數,也沒有繼承,沒有泛型,沒有異常,沒有宏,沒有函數修飾,更沒有線程局部儲存。
但是Go的特點也很鮮明,比如,它擁有協程、自動記憶體回收、包管理系統、一等公民的函數、棧空間管理等。
Go作為靜態類型語言,保證了Go在運行效率、記憶體用量、型別安全都要強於Python和Erlang。
Go的資料類型也更加豐富,除了支援表、字典等複雜的資料結構,還支援指標和介面類型,這是Python和Erlang所沒有的。特別是介面類型特彆強大,它提供了管理類型系統的手段。而指標類型提供了管理記憶體的手段,這讓Go進入底層軟體開發提供了強有力的支援。
Go在面對對象的特性支援上做了很多反思和取捨,它沒有類、虛函數、繼承、泛型等特性。Go語言中物件導向編程的核心是組合和方法(function)。組合很類似於C語言的struct結構體的組合方式,方法類似於Java的介面(Interface),但是使用方法上與對象更加解耦,減少了對對象內部的侵入。Erlang則不支援面對對象編程範式,相比而言,Python對面對對象範式的支援最為全面。
在函數式編程的特性支援上,Erlang作為函數式語言,支援最為全面。但是基本的函數式語言特性,如lambda、高階函數、curry等,三種語言都支援。
控制流程的特性支援上,三種語言都差不多。Erlang支援尾遞迴最佳化,這給它在函數式編程上帶來便利。而Go在通過動態擴充協程棧的方式來支援深度遞迴調用。Python則在深度遞迴調用上經常被爆棧。
Go和Erlang的並行存取模型都來源於CSP,但是Erlang是基於actor和訊息傳遞(mailbox)的並行存取模型,Go是基於goroutine和管道(channel)的並發。不管Erlang的actor還是Go的goroutine,都滿足協程的特點:由程式設計語言實現和調度,使用者態的切換,建立銷毀開銷很小。至於Python,其多線程的切換和調度是基於作業系統實現,而且因為GIL的大坑級存在,是無法真正做到並行。
而且從筆者的並發編程體驗上看,Erlang的函數式編程文法風格和其OTP behavior架構提供的晦澀的回調(callback)使用方法,對大部分的程式員,如C/C++和Java出身的程式員來說,有一定的入門門檻和挑戰。而被稱為“互連網時代的C”的Go,其類C的文法和控制流程實現,以及面對對象的編程範式,編程體驗則好很多。
Go/Python/Erlang語言文法對比
所有的語言特性都需要有形式化的表示方式,Go、Python、Erlang三種語言文法的詳細對比如下(點擊見完整大圖第一部分,第二部分)。這裡(連結)有一個詳細的Go 與 C 的文法對比,這也是我沒有做Go vs. C對比的一個原因。
正如Go語言的設計者之一Rob Pike所說,“軟體的複雜性是乘法級相關的”。這充分體現在語言關鍵詞(keyword)數量的控制上,Go的關鍵詞是最少的,只有25個,而Erlang是27個,Python是31個。從根本上保證了Go語言的簡單易學。
Go語言將資料類型分為四類:基礎類型、複合類型、參考型別和介面類型。基礎類型包括:整型、浮點型、複數、字串和布爾型。複合資料型別有數組和結構體。參考型別包括指標、切片、字典、函數、通道。其他資料類型,如原子(atom)、位元(binary)、元組(tuple)、集合(set)、記錄(record),Go則沒有支援。
Go對C語言的很多文法特性做了改良,正如Rob Pike在《Less is Exponentially More》中提到,Go的“起點: C語言,解決一些明顯的瑕疵、刪除雜質、增加一些缺少的特性。”,比如,把switch/case的case子程式段預設break跳出,case語句支援數值範圍、條件判斷語句;所有類型預設初始化為0,沒有未初始設定變數;把類型放在變數後面的聲明文法(連結),使複雜聲明更加清晰易懂;沒有標頭檔,檔案的編譯以包組織,改善封裝能力;用空介面(interface {})代替void *的位置,提高類型系統能力等等。
Go對函數,方法,介面做了清晰的區分。與Erlang類似,Go的函數作為第一公民。函數可以讓我們將一個語句序列打包為一個單元,然後可以從程式中其它地方多次調用。函數和方法的區別是指有沒有接收器,而不像其他語言那樣是指有沒有傳回值。介面類型具體描述了一系列方法的集合,而空介面interfac{}表示可以接收任意類型。介面的這2中使用方式,用面對對象編程範式來類比的話,可以類比於subtype polymorphism(子類型多態)和ad hoc polymorphism(非多重參數變形)。
樣本可以看出,Go的goroutine就是一個函數,以及在堆上為其分配的一個堆棧。所以它非常廉價,我們可以很輕鬆的建立上萬個goroutine,但它們並不是被作業系統所調度執行。goroutine只能使用channel來發送給指定的goroutine請求來查詢更新變數。這也就是Go的口頭禪“不要使用共用資料來通訊,使用通訊來共用資料”。channel支援容量限制和range迭代器。
Go/Python/Erlang語言詞法對比
Go、Python、Erlang三種語言詞法符號的詳細對比如下(點擊見完整大圖)。Go的詞法符號是3個語言中最多的,有41個,而且符號複用的情況也較多。相對來說,Python最少,只有31個。
Go語言在詞法和代碼格式上採取了很強硬的態度。Go語言只有一種控制可見度的手段:大寫首字母的標識符會從定義它們的包中被匯出,小寫字母的則不會。這種限制包內成員的方式同樣適用於struct或者一個類型的方法。
在檔案命名上,Go也有一定的規範要求,如以_test.go為尾碼名的源檔案是測試檔案,它們是go test測試的一部分;測試檔案中以Test為函數名首碼的函數是測試函數,用於測試程式的一些邏輯行為是否正確;以Benchmark為函數名首碼的函數是基準測試函數,它們用于衡量一些函數的效能。
除了關鍵字,此外,Go還有大約30多個預定義的名字,比如int和true等,主要對應內建的常量、類型和函數。
TDD Go編程樣本
本小節以TDD方式開發一個斐波那契演算法的方式,展示Go的特性、文法和使用方式,如Go的單元測試技術,並發編程、匿名函數、閉包等。
首先單元測試檔案如下:
package mainimport ("testing")func TestFib(t *testing.T) {var testdatas = []struct {n intwant int64}{{0, 0},{1, 1},{2, 1},{3, 2},{4, 3},{16, 987},{32, 2178309},{45, 1134903170},}for _, test := range testdatas {n := test.nwant := test.wantgot := fib(n)if got != want {t.Errorf("fib(%d)=%d, want %d\n", n, got, want)}}}
基於遞迴的實現方案:
func fib1(n int) int64 {if n == 0 || n == 1 {return int64(n)}return fib1(n-1) + fib1(n-2)}
測試結果:
[email protected]$ time go test
PASS
ok _/home/crbsp/alex/go/fib 9.705s
real 0m10.045s
user 0m9.968s
sys 0m0.068s
基於goroutine實現的並發方案:
func fib2(n int) int64 {var got int64var channel = make(chan int64, 2)if n == 0 || n == 1 {return int64(n)}runtime.GOMAXPROCS(2)go func() { channel <- fib1(n - 2) }()go func() { channel <- fib1(n - 1) }()got = <-channelgot += <-channelreturn got}
測試結果:
[email protected]$ time go test
PASS
ok _/home/crbsp/alex/go/fib 6.118s
real 0m6.674s
user 0m10.268s
sys 0m0.148s
基於迭代的實現方案:
func fib3(n int) int64 {var a, b int64a, b = 0, 1for i := 0; i < n; i++ {a, b = b, a+b}return a}
測試結果:
[email protected]$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002s
real 0m0.547s
user 0m0.328s
sys 0m0.172s
基於閉包的實現方案:
func fibWrapper4() func() int64 {var a, b int64a, b = 0, 1return func() int64 {a, b = b, a+breturn a}}func fib4(n int) int64 {var got int64got = 0f := fibWrapper4()for i := 0; i < n; i++ {got = f()}return got}
測試結果:
[email protected]$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002s
real 0m0.411s
user 0m0.260s
sys 0m0.140s
--完--
Go/Python/Erlang程式設計語言對比分析及樣本