本文主要是介紹Go,從語言對比分析的角度切入。之因此選擇與Python、Erlang對比,是由於作爲高級語言,它們語言特性上有較大的類似性,不過最主要的緣由是這幾個我比較熟悉。html
Go的不少語言特性借鑑與它的三個祖先:C,Pascal和CSP。Go的語法、數據類型、控制流等繼承於C,Go的包、面對對象等思想來源於Pascal分支,而Go最大的語言特點,基於管道通訊的協程併發模型,則借鑑於CSP分支。程序員
如《編程語言與範式》一文所說,無論語言如何層出不窮,全部語言的設計離不開2個基本面:控制流和數據類型。爲了提高語言描述能力,語言通常都提供控制抽象和數據抽象。本小節的語言特性對比也從這4個維度入手,詳見下圖(點擊見大圖)。golang
圖中咱們能夠看出,相比於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 與 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的詞法符號是3個語言中最多的,有41個,並且符號複用的狀況也較多。相對來講,Python最少,只有31個。
Go語言在詞法和代碼格式上採起了很強硬的態度。Go語言只有一種控制可見性的手段:大寫首字母的標識符會從定義它們的包中被導出,小寫字母的則不會。這種限制包內成員的方式一樣適用於struct或者一個類型的方法。
在文件命名上,Go也有必定的規範要求,如以_test.go爲後綴名的源文件是測試文件,它們是go test測試的一部分;測試文件中以Test爲函數名前綴的函數是測試函數,用於測試程序的一些邏輯行爲是否正確;以Benchmark爲函數名前綴的函數是基準測試函數,它們用於衡量一些函數的性能。
除了關鍵字,此外,Go還有大約30多個預約義的名字,好比int和true等,主要對應內建的常量、類型和函數。
本小節以TDD方式4次重構開發一個斐波那契算法的方式,來簡單展現Go的特性、語法和使用方式,如Go的單元測試技術,併發編程、匿名函數、閉包等。
首先,看一下TDD最終造成的單元測試文件:
package main import ( "testing" ) func TestFib(t *testing.T) { var testdatas = []struct { n int want int64 }{ {0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {16, 987}, {32, 2178309}, {45, 1134903170}, } for _, test := range testdatas { n := test.n want := test.want got := 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) }
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 9.705sreal 0m10.045s
user 0m9.968s
sys 0m0.068s
基於goroutine實現的併發方案:
func fib2(n int) int64 { var got int64 var 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 = <-channel got += <-channel return got }
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 6.118sreal 0m6.674s
user 0m10.268s
sys 0m0.148s
基於迭代的實現方案:
func fib3(n int) int64 { var a, b int64 a, b = 0, 1 for i := 0; i < n; i++ { a, b = b, a+b } return a }
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002sreal 0m0.547s
user 0m0.328s
sys 0m0.172s
基於閉包的實現方案:
func fibWrapper4() func() int64 { var a, b int64 a, b = 0, 1 return func() int64 { a, b = b, a+b return a } } func fib4(n int) int64 { var got int64 got = 0 f := fibWrapper4() for i := 0; i < n; i++ { got = f() } return got }
測試結果:
crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002sreal 0m0.411s
user 0m0.260s
sys 0m0.140s
--完--