函數(shù)式語(yǔ)言
成都創(chuàng)新互聯(lián)專注于企業(yè)成都全網(wǎng)營(yíng)銷推廣、網(wǎng)站重做改版、靈臺(tái)網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、HTML5、商城網(wǎng)站制作、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為靈臺(tái)等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
Elixir
Elixir 比 Erlang 更容易編寫(xiě),具有 Haskell 等語(yǔ)言的函數(shù)式編程概念。Elixir是基于Erlang 虛擬機(jī)的,其廣為人知的特點(diǎn)是運(yùn)行低延時(shí)、分布式、可容錯(cuò)的系統(tǒng),并成功用于Web開(kāi)發(fā)與嵌入式軟件領(lǐng)域。
Elm
Elm是一種用于構(gòu)建 Web 應(yīng)用程序的函數(shù)式語(yǔ)言。業(yè)內(nèi)一般認(rèn)為,它適用于創(chuàng)建高可交互應(yīng)用,例如復(fù)雜的用戶界面,開(kāi)發(fā)人員可以通過(guò) Elm 快速編寫(xiě)富有表現(xiàn)力的系統(tǒng)。Elm 也以沒(méi)有運(yùn)行時(shí)異常而聞名。
PureScript
PureScript是一種可編譯為 JavaScript 的純函數(shù)式編程語(yǔ)言。與 Haskell 最相似的是,PureScript 最適合用于開(kāi)發(fā) Web 應(yīng)用程序和服務(wù)器端應(yīng)用程序。
PureScript 支持類型推斷,與其他語(yǔ)言相比,需要明顯類型注釋要少得多。
Swift
Swift是一種由蘋(píng)果公司開(kāi)發(fā)的通用編譯編程語(yǔ)言,最早的設(shè)想是替代上一代編程語(yǔ)言O(shè)bjective-C ,過(guò)程中結(jié)合了Objective-C、Rust、Ruby 和 Python等語(yǔ)言的編程思想。目前Swift用于開(kāi)發(fā)蘋(píng)果自己的手機(jī)、服務(wù)器、臺(tái)式機(jī)上的應(yīng)用軟件。
程序語(yǔ)言
Go
Go語(yǔ)言是由谷歌公司創(chuàng)造的類似C風(fēng)格的語(yǔ)言。Go 比 C++ 或 Java 更簡(jiǎn)潔,比 Ruby 或 Python 更安全。
一些缺點(diǎn): 編碼要求嚴(yán)格。比如,不能混用符號(hào)和無(wú)符號(hào)整數(shù)。還有一個(gè)明顯的遺漏,Go語(yǔ)言沒(méi)有泛型和繼承。
但Go語(yǔ)言的優(yōu)勢(shì)同樣明顯,簡(jiǎn)單且易于使用。Go語(yǔ)言擅長(zhǎng)于網(wǎng)絡(luò)和多線程方面的編程。
面向?qū)ο笳Z(yǔ)言
DART
Dart同樣來(lái)自谷歌公司具有C語(yǔ)言風(fēng)格。Dart可以輕松編寫(xiě)JavaScript、Java for Android、本地機(jī)器代碼或獨(dú)立的 Dart 虛擬機(jī)。它還可以運(yùn)行后端代碼。
Dart 非常適合使用事件驅(qū)動(dòng)代碼構(gòu)建用戶界面。根據(jù)Dart 團(tuán)隊(duì)成員的說(shuō)法,Dart的優(yōu)勢(shì):可選的靜態(tài)類型、最小的編譯時(shí)錯(cuò)誤和強(qiáng)大的內(nèi)置編輯器。
Pony
Pony是一種基于無(wú)數(shù)據(jù)競(jìng)爭(zhēng)類型和垃圾收集的語(yǔ)言,并使用 actor 模型以及稱為引用功能的東西。
你可以把 Pony 想象成某種“Rust 遇上 Erlang”的復(fù)合體,沒(méi)有鎖,高并發(fā)是其主要優(yōu)點(diǎn)。
Pony 的缺點(diǎn)是 API 穩(wěn)定性低、很少有高質(zhì)量的第三方庫(kù)和有限的本地工具。
TypeScript
TypeScript是一個(gè)基于 JavaScript 靜態(tài)類型定義構(gòu)建,并由微軟維護(hù)且開(kāi)源編程語(yǔ)言。Visual Studio Code 或Visual Studio 是推薦的IDE編輯器,微軟大廠的用戶體驗(yàn)和錯(cuò)誤檢查也不用懷疑。
復(fù)合編程語(yǔ)言
Hack
Hack是一種作為 PHP 方言的 HipHop 虛擬機(jī)的編程語(yǔ)言。于 2014 年由Facebook創(chuàng)建,允許程序員同時(shí)使用靜態(tài)和動(dòng)態(tài)類型(也稱為漸進(jìn)類型),這為編碼提供了靈活性。
Julia
Julia是一種高級(jí)通用編程語(yǔ)言,用于計(jì)算科學(xué)和數(shù)值分析。Julia 以動(dòng)態(tài)類型和可重現(xiàn)的高性能特性而聞名。
Julia 在數(shù)據(jù)可視化和機(jī)器學(xué)習(xí)等方面都有大量用途。事實(shí)上,它被英國(guó)保險(xiǎn)公司 Aviva 用于風(fēng)險(xiǎn)計(jì)算,紐約聯(lián)邦儲(chǔ)備銀行用于金融建模,甚至氣候建模聯(lián)盟用于氣候變化建模。它擁有Fortran、C++、R、Java、C 、Python等的接口,這使其成為最受追捧的新語(yǔ)言之一。
Kotlin
Kotlin是運(yùn)行在 Java 虛擬機(jī)中的更快、更流暢的 Java 版本。它現(xiàn)在是Android 開(kāi)發(fā)的首選語(yǔ)言。根據(jù) Android 開(kāi)發(fā)者網(wǎng)站顯示,程序員正轉(zhuǎn)而采用 Kotlin,因?yàn)樵撜Z(yǔ)言的樣板代碼更少,空指針異常更少,并且與 Java 有互操作性。
Kotlin 可用于在 iOS 和 Android 上運(yùn)行的應(yīng)用程序、不使用額外運(yùn)行時(shí)或虛擬機(jī)。
Nim
Nim是一種優(yōu)先考慮可讀性的靜態(tài)類型語(yǔ)言。通過(guò)結(jié)合多種語(yǔ)言的特性,Nim 為程序員提供了速度和易用性。
它帶有 JavaScript 后端、分散的包管理、自動(dòng)內(nèi)存管理、C 和 C++ 庫(kù)的綁定以及用于調(diào)試的回溯。作為一種語(yǔ)言,Nim 是有限的,但它包含一組元編程功能,如泛型、模板和宏,因此開(kāi)發(fā)人員可以在避免冗長(zhǎng)代碼的同時(shí)以不同的風(fēng)格工作。
OCaml作為此列表中較舊的語(yǔ)言,OCaml是一種多范式語(yǔ)言——既有函數(shù)式、命令式和類型安全,也具有面向?qū)ο蠊δ堋?/p>
OCaml 的一些優(yōu)勢(shì):定義數(shù)據(jù)類型很容易。默認(rèn)情況下,所有變量都是不可變的。API 穩(wěn)定,具有良好的庫(kù)向后兼容性。該語(yǔ)言還為獨(dú)立應(yīng)用程序提供自動(dòng)內(nèi)存管理和單獨(dú)編譯。
Reason
如果比JavaScript 更快、更簡(jiǎn)單且類型安全會(huì)怎樣?
這就是創(chuàng)建Reason的 Facebook 開(kāi)發(fā)者想要回答的問(wèn)題。不過(guò),他并沒(méi)有從頭開(kāi)始構(gòu)建一種新語(yǔ)言,而是采用了 OCaml,并將其調(diào)整為類似于 JavaScript。
Reason使用項(xiàng)目 BucketScript編譯為 JavaScript,并且可以訪問(wèn) 80% 的 JavaScript 工具和生態(tài)系統(tǒng)。它還可以編譯為準(zhǔn)系統(tǒng)、iOS、Android 和微控制器。
Red
Red是一種最初旨在克服 Rebol 語(yǔ)言限制的編程語(yǔ)言。Red 于 2011 年推出,受 Rebol、Lua 和 Scala 等語(yǔ)言的影響,對(duì)高級(jí)和低級(jí)編程都很有用。
該語(yǔ)言可用于開(kāi)發(fā)從高級(jí) GUI 到低級(jí)操作系統(tǒng)的所有方面。Red 擁有人性化的語(yǔ)法、低內(nèi)存占用和垃圾收集等優(yōu)點(diǎn)。
Rust
Rust解決了一些與 Go 相同的問(wèn)題,如系統(tǒng)級(jí)別的線程和進(jìn)程安全,,但Rust 更像 C 風(fēng)格的語(yǔ)法
但Rust語(yǔ)言的缺點(diǎn):靜態(tài)類型和缺乏垃圾收集
Rust可直接訪問(wèn)內(nèi)存意味著程序員可以編寫(xiě)低級(jí)代碼,如操作系統(tǒng)內(nèi)核。Rust 也非常適合嵌入式設(shè)備、網(wǎng)絡(luò)服務(wù)和命令行編寫(xiě)。
在本節(jié)中,您將添加通用函數(shù)調(diào)用的修改版本,進(jìn)行小的更改以簡(jiǎn)化調(diào)用代碼。您將刪除在這種情況下不需要的類型參數(shù)。
當(dāng) Go 編譯器可以推斷您要使用的類型時(shí),您可以在調(diào)用代碼中省略類型參數(shù)。編譯器從函數(shù)參數(shù)的類型推斷類型參數(shù)。
請(qǐng)注意,這并不總是可能的。例如,如果您需要調(diào)用沒(méi)有參數(shù)的泛型函數(shù),則需要在函數(shù)調(diào)用中包含類型參數(shù)。
在 main.go 中,在您已有的代碼下方,粘貼以下代碼。
在此代碼中:
(1)調(diào)用泛型函數(shù),省略類型參數(shù)。
從包含 main.go 的目錄中的命令行,運(yùn)行代碼。
接下來(lái),您將通過(guò)將整數(shù)和浮點(diǎn)數(shù)的并集捕獲到您可以重用的類型約束(例如從其他代碼中)來(lái)進(jìn)一步簡(jiǎn)化函數(shù)。
正如您將在本節(jié)中看到的,約束接口也可以引用特定類型。
1、編寫(xiě)代碼
在此代碼中:
b.在您已有的函數(shù)下方,粘貼以下通用 SumNumbers函數(shù)。
在此代碼中:
c.在 main.go 中,在您已有的代碼下方,粘貼以下代碼。
在此代碼中:
(1)調(diào)用SumNumbers打印每個(gè)map的總和。
與上一節(jié)一樣,在調(diào)用泛型函數(shù)時(shí)省略了類型參數(shù)(方括號(hào)中的類型名稱)。Go 編譯器可以從其他參數(shù)推斷類型參數(shù)。
從包含 main.go 的目錄中的命令行,運(yùn)行代碼。
做得很好!您剛剛學(xué)習(xí)了 Go 中的泛型。
1. 部署簡(jiǎn)單
Go
編譯生成的是一個(gè)靜態(tài)可執(zhí)行文件,除了glibc外沒(méi)有其他外部依賴。這讓部署變得異常方便:目標(biāo)機(jī)器上只需要一個(gè)基礎(chǔ)的系統(tǒng)和必要的管理、監(jiān)控工具,完全不需要操心應(yīng)用所需的各種包、庫(kù)的依賴關(guān)系,大大減輕了維護(hù)的負(fù)擔(dān)。
2. 并發(fā)性好
Goroutine和channel使得編寫(xiě)高并發(fā)的服務(wù)端軟件變得相當(dāng)容易,很多情況下完全不需要考慮鎖機(jī)制以及由此帶來(lái)的各種問(wèn)題。單個(gè)Go應(yīng)用也能有效的利用多個(gè)CPU核,并行執(zhí)行的性能好。
3. 良好的語(yǔ)言設(shè)計(jì)
從學(xué)術(shù)的角度講Go語(yǔ)言其實(shí)非常平庸,不支持許多高級(jí)的語(yǔ)言特性;但從工程的角度講,Go的設(shè)計(jì)是非常優(yōu)秀的:規(guī)范足夠簡(jiǎn)單靈活,有其他語(yǔ)言基礎(chǔ)的程序員都能迅速上手。更重要的是
Go 自帶完善的工具鏈,大大提高了團(tuán)隊(duì)協(xié)作的一致性。
4. 執(zhí)行性能好
雖然不如 C 和 Java,但相比于其他編程語(yǔ)言,其執(zhí)行性能還是很好的,適合編寫(xiě)一些瓶頸業(yè)務(wù),內(nèi)存占用也非常省。
Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成為現(xiàn)實(shí)。Go 團(tuán)隊(duì)實(shí)施了一個(gè)看起來(lái)比較穩(wěn)定的設(shè)計(jì)草案,并且正以源到源翻譯器原型的形式獲得關(guān)注。本文講述的是泛型的最新設(shè)計(jì),以及如何自己嘗試泛型。
例子
FIFO Stack
假設(shè)你要?jiǎng)?chuàng)建一個(gè)先進(jìn)先出堆棧。沒(méi)有泛型,你可能會(huì)這樣實(shí)現(xiàn):
type?Stack?[]interface{}func?(s?Stack)?Peek()?interface{}?{
return?s[len(s)-1]
}
func?(s?*Stack)?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack)?Push(value?interface{})?{
*s?=?
append(*s,?value)
}
但是,這里存在一個(gè)問(wèn)題:每當(dāng)你 Peek 項(xiàng)時(shí),都必須使用類型斷言將其從 interface{} 轉(zhuǎn)換為你需要的類型。如果你的堆棧是 *MyObject 的堆棧,則意味著很多 s.Peek().(*MyObject)這樣的代碼。這不僅讓人眼花繚亂,而且還可能引發(fā)錯(cuò)誤。比如忘記 * 怎么辦?或者如果您輸入錯(cuò)誤的類型怎么辦?s.Push(MyObject{})` 可以順利編譯,而且你可能不會(huì)發(fā)現(xiàn)到自己的錯(cuò)誤,直到它影響到你的整個(gè)服務(wù)為止。
通常,使用 interface{} 是相對(duì)危險(xiǎn)的。使用更多受限制的類型總是更安全,因?yàn)榭梢栽诰幾g時(shí)而不是運(yùn)行時(shí)發(fā)現(xiàn)問(wèn)題。
泛型通過(guò)允許類型具有類型參數(shù)來(lái)解決此問(wèn)題:
type?Stack(type?T)?[]Tfunc?(s?Stack(T))?Peek()?T?{
return?s[len(s)-1]
}
func?(s?*Stack(T))?Pop()?{
*s?=?(*s)[:
len(*s)-1]
}
func?(s?*Stack(T))?Push(value?T)?{
*s?=?
append(*s,?value)
}
這會(huì)向 Stack 添加一個(gè)類型參數(shù),從而完全不需要 interface{}。現(xiàn)在,當(dāng)你使用 Peek() 時(shí),返回的值已經(jīng)是原始類型,并且沒(méi)有機(jī)會(huì)返回錯(cuò)誤的值類型。這種方式更安全,更容易使用。(譯注:就是看起來(lái)更丑陋,^-^)
此外,泛型代碼通常更易于編譯器優(yōu)化,從而獲得更好的性能(以二進(jìn)制大小為代價(jià))。如果我們對(duì)上面的非泛型代碼和泛型代碼進(jìn)行基準(zhǔn)測(cè)試,我們可以看到區(qū)別:
type?MyObject?struct?{
X?
int
}
var?sink?MyObjectfunc?BenchmarkGo1(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek().(MyObject)
}
}
func?BenchmarkGo2(b?*testing.B)?{
for?i?:=?0;?i??b.N;?i++?{
var?s?Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink?=?s.Peek()
}
}
結(jié)果:
BenchmarkGo1BenchmarkGo1-16?????12837528?????????87.0?ns/op???????48?B/op????????2?allocs/opBenchmarkGo2BenchmarkGo2-16?????28406479?????????41.9?ns/op???????24?B/op????????2?allocs/op
在這種情況下,我們分配更少的內(nèi)存,同時(shí)泛型的速度是非泛型的兩倍。
合約(Contracts)
上面的堆棧示例適用于任何類型。但是,在許多情況下,你需要編寫(xiě)僅適用于具有某些特征的類型的代碼。例如,你可能希望堆棧要求類型實(shí)現(xiàn) String() 函數(shù)
有好幾次,當(dāng)我想起來(lái)的時(shí)候,總是會(huì)問(wèn)自己:我為什么要放棄Go語(yǔ)言?這個(gè)決定是正確的嗎?是明智和理性的嗎?其實(shí)我一直在認(rèn)真思考這個(gè)問(wèn)題。
開(kāi)門(mén)見(jiàn)山地說(shuō),我當(dāng)初放棄Go語(yǔ)言(golang),就是因?yàn)閮蓚€(gè)“不爽”:第一,對(duì)Go語(yǔ)言本身不爽;第二,對(duì)Go語(yǔ)言社區(qū)里的某些人不爽。毫無(wú)疑問(wèn),這是非常主觀的結(jié)論。轉(zhuǎn)載
1.1 不允許左花括號(hào)另起一行
1.2 編譯器莫名其妙地給行尾加上分號(hào)
1.3 極度強(qiáng)調(diào)編譯速度,不惜放棄本應(yīng)提供的功能
1.4 錯(cuò)誤處理機(jī)制太原始
1.5 垃圾回收器(GC)不完善、有重大缺陷
1.6 禁止未使用變量和多余import
1.7 創(chuàng)建對(duì)象的方式太多令人糾結(jié)
1.8 對(duì)象沒(méi)有構(gòu)造函數(shù)和析構(gòu)函數(shù)
1.9 defer語(yǔ)句的語(yǔ)義設(shè)定不甚合理
1.10 許多語(yǔ)言內(nèi)置設(shè)施不支持用戶定義的類型
1.11 沒(méi)有泛型支持,常見(jiàn)數(shù)據(jù)類型接口丑陋
1.12 實(shí)現(xiàn)接口不需要明確聲明
1.13 省掉小括號(hào)卻省不掉花括號(hào)
1.14 編譯生成的可執(zhí)行文件尺寸非常大
1.15 不支持動(dòng)態(tài)加載類庫(kù)
此篇文章流傳甚廣, 其實(shí)里面沒(méi)啥干貨, 而且里面很多觀點(diǎn)是有問(wèn)題的. 這個(gè)文章在 golang-china 很早就討論過(guò)了.
最近因?yàn)?Rust 1.0 和 1.1 的發(fā)布, 導(dǎo)致這個(gè)文章又出來(lái)毒害讀者.
所以寫(xiě)了這篇反駁文章, 指出其中的問(wèn)題.
有好幾次,當(dāng)我想起來(lái)的時(shí)候,總是會(huì)問(wèn)自己:我為什么要放棄Go語(yǔ)言?這個(gè)決定是正確的嗎?是明智和理性的嗎?其實(shí)我一直在認(rèn)真思考這個(gè)問(wèn)題。
開(kāi)門(mén)見(jiàn)山地說(shuō),我當(dāng)初放棄Go語(yǔ)言(golang),就是因?yàn)閮蓚€(gè)“不爽”:第一,對(duì)Go語(yǔ)言本身不爽;第二,對(duì)Go語(yǔ)言社區(qū)里的某些人不爽。毫無(wú)疑問(wèn),這是非常主觀的結(jié)論。但是我有足夠詳實(shí)的客觀的論據(jù),用以支撐這個(gè)看似主觀的結(jié)論。
文末附有本文更新日志。
確實(shí)是非常主觀的結(jié)論, 因?yàn)槔锩嬗胁簧儆袉?wèn)題的觀點(diǎn)(用來(lái)忽悠Go小白還行).
第0節(jié):我的Go語(yǔ)言經(jīng)歷
先說(shuō)說(shuō)我的經(jīng)歷吧,以避免被無(wú)緣無(wú)故地當(dāng)作Go語(yǔ)言的低級(jí)黑。
2009年底,Go語(yǔ)言(golang)第一個(gè)公開(kāi)版本發(fā)布,籠罩著“Google公司制造”的光環(huán),吸引了許多慕名而來(lái)的嘗鮮者,我(Liigo)也身居其中,籠統(tǒng)的看了一些Go語(yǔ)言的資料,學(xué)習(xí)了基礎(chǔ)的教程,因?qū)ζ湔Z(yǔ)法中的分號(hào)和花括號(hào)不滿,很快就遺忘掉了,沒(méi)拿它當(dāng)一回事。
在2009年Go剛發(fā)布時(shí), 確實(shí)是因?yàn)椤癎oogle公司制造”的光環(huán)而吸引了(包括文章作者和諸多IT記者)很多低級(jí)的嘗鮮者.
還好, 經(jīng)過(guò)5年的發(fā)展, 這些純粹因?yàn)楣猸h(huán)來(lái)的投機(jī)者所剩已經(jīng)不多了(Google趨勢(shì)).
目前, 真正的Go用戶早就將Go用于實(shí)際的生產(chǎn)了.
說(shuō)到 其語(yǔ)法中的分號(hào)和花括號(hào)不滿, 我想說(shuō)這只是你的 個(gè)人主觀感受, 還有很多人對(duì)Go的分號(hào)和花括號(hào)很滿意,
包括水果公司的的 Swift 的語(yǔ)言設(shè)計(jì)者也很滿意這種風(fēng)格(Swift中的分號(hào)和花括號(hào)和Go基本相同).
如果只談 個(gè)人主觀感受, 我也可以說(shuō) Rust 的 fn 縮寫(xiě)也很蛋疼!
兩年之后,2011年底,Go語(yǔ)言發(fā)布1.0的計(jì)劃被提上日程,相關(guān)的報(bào)道又多起來(lái),我再次關(guān)注它,重新評(píng)估之后決定深入?yún)⑴cGo語(yǔ)言。我訂閱了其users、nuts、dev、commits等官方郵件組,堅(jiān)持每天閱讀其中的電子郵件,以及開(kāi)發(fā)者提交的每一次源代碼更新,給Go提交了許多改進(jìn)意見(jiàn),甚至包括修改Go語(yǔ)言編譯器源代碼直接參與開(kāi)發(fā)任務(wù)。如此持續(xù)了數(shù)月時(shí)間。
這個(gè)到是事實(shí), 在 golang-china 有不少吵架的帖子, 感興趣的可以去挖下, 我就不展開(kāi)說(shuō)了.
到2012年初,Go 1.0發(fā)布,語(yǔ)言和標(biāo)準(zhǔn)庫(kù)都已經(jīng)基本定型,不可能再有大幅改進(jìn),我對(duì)Go語(yǔ)言未能在1.0定型之前更上一個(gè)臺(tái)階、實(shí)現(xiàn)自我突破,甚至帶著諸多明顯缺陷走向1.0,感到非常失望,因而逐漸疏遠(yuǎn)了它(所以Go 1.0之后的事情我很少關(guān)心)。后來(lái)看到即將發(fā)布的Go 1.1的Release Note,發(fā)現(xiàn)語(yǔ)言層面沒(méi)有太大改變,只是在庫(kù)和工具層面有所修補(bǔ)和改進(jìn),感到它尚在幼年就失去成長(zhǎng)的動(dòng)力,越發(fā)失望。外加Go語(yǔ)言社區(qū)里的某些人,其中也包括Google公司負(fù)責(zé)開(kāi)發(fā)Go語(yǔ)言的某些人,其態(tài)度、言行,讓我極度厭惡,促使我決絕地離棄Go語(yǔ)言。
真的不清楚樓主說(shuō)的可以在 Go1.0 之前短時(shí)間內(nèi)能實(shí)現(xiàn)的 重大改進(jìn)和諸多明顯缺陷 是什么.
如果是樓主說(shuō)前面的 其語(yǔ)法中的分號(hào)和花括號(hào)不滿 之類的重大改進(jìn), 我只能說(shuō)這只是你的 個(gè)人主觀感受 而已,
你的很多想法只能說(shuō)服你自己, 沒(méi)辦法說(shuō)服其他絕大部分人(不要以為像C++或Rust那樣什么特性都有就NB了, 各種NB特性加到一起只能是 要你命3000, 而絕對(duì)不會(huì)是什么 銀彈).
Go 1.1的Release Note,發(fā)現(xiàn)語(yǔ)言層面沒(méi)有太大改變. 語(yǔ)言層沒(méi)有改變是是因?yàn)?Go1 作出的向后兼容的承諾. 對(duì)于工業(yè)級(jí)的語(yǔ)言來(lái)說(shuō), Go1 這個(gè)只能是優(yōu)點(diǎn). 如果連語(yǔ)言層在每個(gè)版本都會(huì)出現(xiàn)諸多大幅改進(jìn), 那誰(shuí)還敢用Go語(yǔ)言來(lái)做生產(chǎn)開(kāi)發(fā)呢(我承認(rèn)Rust的改動(dòng)很大膽, 但也說(shuō)明了Rust還處于比較幼稚和任性的階段)?
說(shuō) Go語(yǔ)言社區(qū)里的某些人固執(zhí) 的觀點(diǎn)我是同意的. 但是這些 固執(zhí) 的人是可以講道理的, 但是他們對(duì)很多東西的要求很高(特別是關(guān)于Go的設(shè)計(jì)哲學(xué)部分).
只要你給的建議有依據(jù)(語(yǔ)言的設(shè)計(jì)哲學(xué)是另外一回事情), 他們絕對(duì)不會(huì)盲目的拒絕(只是討論的周期會(huì)比較長(zhǎng)).
關(guān)于樓主提交的給Go文件添加BOM的文章, 需要補(bǔ)充說(shuō)明下.
在Go1.0發(fā)布的時(shí)候, Go語(yǔ)言的源文件(.go)明確要求必須是UTF8編碼的, 而且是無(wú)BOM的UTF8編碼的.
注意: 這個(gè) 無(wú)BOM的UTF8編碼 的限制僅僅是 針對(duì) Go語(yǔ)言的源文件(.go).
這個(gè)限制并不是說(shuō)不允許用戶處理帶BOM的UTF8的txt文件!
我覺(jué)得對(duì)于寫(xiě)Go程序來(lái)說(shuō), 這個(gè)限制是沒(méi)有任何問(wèn)題的, 到目前為止, 我還從來(lái)沒(méi)有使用過(guò)帶BOM的.go文件.
不僅是因?yàn)閹OM的.go文件沒(méi)有太多的意義, 而且有很多的缺陷.
BOM的原意是用來(lái)表示編碼是大端還是小端的, 主要用于UTF16和UTF32. 對(duì)于 UTF8 來(lái)說(shuō), BOM 沒(méi)有任何存在的意義(正是Go的2個(gè)作者發(fā)明了UTF8, 徹底解決了全球的編碼問(wèn)題).
但是, 在現(xiàn)實(shí)中, 因?yàn)镸S的txt記事本, 對(duì)于中文環(huán)境會(huì)將txt(甚至是C/C++源文件)當(dāng)作GBK編碼(GBK是個(gè)爛編碼),
為了區(qū)別到底是GBK還是UTF8, MS的記事本在前面加了BOM這個(gè)垃圾(被GBK占了茅坑), 這里的bom已經(jīng)不是表示字節(jié)序本意了. 不知道有沒(méi)有人用ms的記事本寫(xiě)網(wǎng)頁(yè), 然后生成一個(gè)帶bom的utf8網(wǎng)頁(yè)肯定很有意思.
這是MS的記事本的BUG: 它不支持生成無(wú)BOM的UTF8編碼的文本文件!
這些是現(xiàn)實(shí)存在的帶BOM的UTF8編碼的文本文件, 但是它們肯定都不是Go語(yǔ)言源文件!
所以說(shuō), Go語(yǔ)言的源文件即使強(qiáng)制限制了無(wú)BOM的UTF8編碼要求, 也是沒(méi)有任何問(wèn)題的(而且我還希望有這個(gè)限制).
雖然后來(lái)Go源文件接受帶BOM的UTF8了, 但是運(yùn)行 go fmt 之后, 還是會(huì)刪除掉BOM的(因?yàn)锽OM就是然并卵). 也就是說(shuō) 帶 BOM 的 Go 源文件是不符合 Go語(yǔ)言的編碼風(fēng)格的, go fmt 會(huì)強(qiáng)制刪除 BOM 頭.
前面說(shuō)了BOM是MS帶來(lái)的垃圾, 但是BOM的UTF8除了然并卵之外還有很多問(wèn)題, 因?yàn)锽OM在string的開(kāi)頭嵌入了垃圾,
導(dǎo)致正則表達(dá)式, string的鏈接運(yùn)算等操作都被會(huì)被BOM這個(gè)垃圾所污染. 對(duì)于.go語(yǔ)言, 即使代碼完全一樣, 有BOM和無(wú)BOM會(huì)導(dǎo)致文件的MD5之類的校驗(yàn)碼不同.
所以, 我覺(jué)得Go用戶不用糾結(jié)BOM這個(gè)無(wú)關(guān)緊要的東西.
在上一個(gè)10年,我(Liigo)在我所屬的公司里,深度參與了兩個(gè)編程語(yǔ)言項(xiàng)目的開(kāi)發(fā)。我想,對(duì)于如何判斷某個(gè)編程語(yǔ)言的優(yōu)劣,或者說(shuō)至少對(duì)于如何判斷某個(gè)編程語(yǔ)言是否適合于我自己,我應(yīng)該還是有一點(diǎn)發(fā)言權(quán)的。
第1節(jié):我為什么對(duì)Go語(yǔ)言不爽?
Go語(yǔ)言有很多讓我不爽之處,這里列出我現(xiàn)在還能記起的其中一部分,排名基本上不分先后。讀者們耐心地看完之后,還能淡定地說(shuō)一句“我不在乎”嗎?
1.1 不允許左花括號(hào)另起一行
關(guān)于對(duì)花括號(hào)的擺放,在C語(yǔ)言、C++、Java、C#等社區(qū)中,十余年來(lái)存在持續(xù)爭(zhēng)議,從未形成一致意見(jiàn)。在我看來(lái),這本來(lái)就是主觀傾向很重的抉擇,不違反原則不涉及是非的情況下,不應(yīng)該搞一刀切,讓程序員或團(tuán)隊(duì)自己選擇就足夠了。編程語(yǔ)言本身強(qiáng)行限制,把自己的喜好強(qiáng)加給別人,得不償失。無(wú)論傾向于其中任意一種,必然得罪與其對(duì)立的一群人。雖然我現(xiàn)在已經(jīng)習(xí)慣了把左花括號(hào)放在行尾,但一想到被禁止其他選擇,就感到十分不爽。Go語(yǔ)言這這個(gè)問(wèn)題上,沒(méi)有做到“團(tuán)結(jié)一切可以團(tuán)結(jié)的力量”不說(shuō),還有意給自己樹(shù)敵,太失敗了。
我覺(jué)得Go最偉大的發(fā)明是 go fmt, 從此Go用戶不會(huì)再有花括弧的位置這種無(wú)聊爭(zhēng)論了(當(dāng)然也少了不少灌水和上tiobe排名的機(jī)會(huì)).
是這優(yōu)點(diǎn), Swift 語(yǔ)言也使用和 Go 類似的風(fēng)格(當(dāng)然樓主也可能鄙視swift的作者).
1.2 編譯器莫名其妙地給行尾加上分號(hào)
對(duì)Go語(yǔ)言本身而言,行尾的分號(hào)是可以省略的。但是在其編譯器(gc)的實(shí)現(xiàn)中,為了方便編譯器開(kāi)發(fā)者,卻在詞法分析階段強(qiáng)行添加了行尾的分號(hào),反過(guò)來(lái)又影響到語(yǔ)言規(guī)范,對(duì)“怎樣添加分號(hào)”做出特殊規(guī)定。這種變態(tài)做法前無(wú)古人。在左花括號(hào)被意外放到下一行行首的情況下,它自動(dòng)在上一行行尾添加的分號(hào),會(huì)導(dǎo)致莫名其妙的編譯錯(cuò)誤(Go 1.0之前),連它自己都解釋不明白。如果實(shí)在處理不好分號(hào),干脆不要省略分號(hào)得了;或者,Scala和JavaScript的編譯器是開(kāi)源的,跟它們學(xué)學(xué)怎么處理省略行尾分號(hào)可以嗎?
又是樓主的 個(gè)人主觀感受, 不過(guò)我很喜歡這個(gè)特性. Swift 語(yǔ)言也是類似.
1.3 極度強(qiáng)調(diào)編譯速度,不惜放棄本應(yīng)提供的功能
程序員是人不是神,編碼過(guò)程中免不了因?yàn)榇笠饣蚴韬龇敢恍╁e(cuò)。其中有一些,是大家集體性的很容易就中招的錯(cuò)誤(Go語(yǔ)言里的例子我暫時(shí)想不起來(lái),C++里的例子有“基類析構(gòu)函數(shù)不是虛函數(shù)”)。這時(shí)候編譯器應(yīng)該站出來(lái),多做一些檢查、約束、核對(duì)性工作,盡量阻止常規(guī)錯(cuò)誤的發(fā)生,盡量不讓有潛在錯(cuò)誤的代碼編譯通過(guò),必要時(shí)給出一些警告或提示,讓程序員留意。編譯器不就是機(jī)器么,不就是應(yīng)該多做臟活累活雜活、減少人的心智負(fù)擔(dān)么?編譯器多做一項(xiàng)檢查,可能會(huì)避免數(shù)十萬(wàn)程序員今后多年內(nèi)無(wú)數(shù)次犯同樣的錯(cuò)誤,節(jié)省的時(shí)間不計(jì)其數(shù),這是功德無(wú)量的好事。但是Go編譯器的作者們可不這么想,他們不愿意自己多花幾個(gè)小時(shí)給編譯器增加新功能,覺(jué)得那是虧本,反而減慢了編譯速度。他們以影響編譯速度為由,拒絕了很多對(duì)編譯器改進(jìn)的要求。典型的因噎廢食。強(qiáng)調(diào)編譯速度固然值得贊賞,但如果因此放棄應(yīng)有的功能,我不贊成。
編譯速度是很重要的, 如果編譯速度夠慢, 語(yǔ)言再好也不會(huì)有人使用的.
比如C/C++的增量編譯/預(yù)編譯頭文件/并發(fā)編譯都是為了提高編譯速度.
Rust1.1 也號(hào)稱 比 1.0 的編譯時(shí)間減少了32% (注意: 不是運(yùn)行速度).
當(dāng)然, Go剛面世的時(shí)候, 編譯速度是其中的一個(gè)設(shè)計(jì)目標(biāo).
不過(guò)我想樓主, 可能想說(shuō)的是因?yàn)榫幾g器自己添加分號(hào)而導(dǎo)致的編譯錯(cuò)誤的問(wèn)題.
我覺(jué)得Go中 { 不能另起一行是語(yǔ)言特性, 如果修復(fù)這個(gè)就是引入了新的錯(cuò)誤.
其他的我真想不起來(lái)還有哪些 調(diào)編譯速度,不惜放棄本應(yīng)提供的功能 (不要提泛型, 那是因?yàn)檫€沒(méi)有好的設(shè)計(jì)).
1.4 錯(cuò)誤處理機(jī)制太原始
在Go語(yǔ)言中處理錯(cuò)誤的基本模式是:函數(shù)通常返回多個(gè)值,其中最后一個(gè)值是error類型,用于表示錯(cuò)誤類型極其描述;調(diào)用者每次調(diào)用完一個(gè)函數(shù),都需要檢查這個(gè)error并進(jìn)行相應(yīng)的錯(cuò)誤處理:if err != nil { /*這種代碼寫(xiě)多了不想吐么*/ }。此模式跟C語(yǔ)言那種很原始的錯(cuò)誤處理相比如出一轍,并無(wú)實(shí)質(zhì)性改進(jìn)。實(shí)際應(yīng)用中很容易形成多層嵌套的if else語(yǔ)句,可以想一想這個(gè)編碼場(chǎng)景:先判斷文件是否存在,如果存在則打開(kāi)文件,如果打開(kāi)成功則讀取文件,如果讀取成功再寫(xiě)入一段數(shù)據(jù),最后關(guān)閉文件,別忘了還要處理每一步驟中出現(xiàn)錯(cuò)誤的情況,這代碼寫(xiě)出來(lái)得有多變態(tài)、多丑陋?實(shí)踐中普遍的做法是,判斷操作出錯(cuò)后提前return,以避免多層花括號(hào)嵌套,但這么做的后果是,許多錯(cuò)誤處理代碼被放在前面突出的位置,常規(guī)的處理邏輯反而被掩埋到后面去了,代碼可讀性極差。而且,error對(duì)象的標(biāo)準(zhǔn)接口只能返回一個(gè)錯(cuò)誤文本,有時(shí)候調(diào)用者為了區(qū)分不同的錯(cuò)誤類型,甚至需要解析該文本。除此之外,你只能手工強(qiáng)制轉(zhuǎn)換error類型到特定子類型(靜態(tài)類型的優(yōu)勢(shì)沒(méi)了)。至于panic - recover機(jī)制,致命的缺陷是不能跨越庫(kù)的邊界使用,注定是一個(gè)半成品,最多只能在自己的pkg里面玩一玩。Java的異常處理雖然也有自身的問(wèn)題(比如Checked Exceptions),但總體上還是比Go的錯(cuò)誤處理高明很多。
話說(shuō), 軟件開(kāi)發(fā)都發(fā)展了半個(gè)世紀(jì), 還是無(wú)實(shí)質(zhì)性改進(jìn). 不要以為弄一個(gè)異常的語(yǔ)法糖就是革命了.
我只能說(shuō)錯(cuò)誤和異常是2個(gè)不同的東西, 將所有錯(cuò)誤當(dāng)作異常那是SB行為.
正因?yàn)橛挟惓_@個(gè)所謂的銀彈, 導(dǎo)致很多等著別人幫忙擦屁股的行為(注意 shit 函數(shù)拋出的絕對(duì)不會(huì)是一種類型的 shit, 而被其間接調(diào)用的各種 xxx_shit 也可能拋出各種類型的異常, 這就導(dǎo)致 catch 失控了):
int main() {
try {
shit();
} catch( /* 到底有幾千種 shit ? */) {
...
}
}
Go的建議是 panic - recover 不跨越邊界, 也就是要求正常的錯(cuò)誤要由pkg的處理掉.
這是負(fù)責(zé)任的行為.
再說(shuō)Go是面向并發(fā)的編程語(yǔ)言, 在海量的 goroutine 中使用 try/catch 是不是有一種不倫不類的感覺(jué)呢?
1.5 垃圾回收器(GC)不完善、有重大缺陷
在Go 1.0前夕,其垃圾回收器在32位環(huán)境下有內(nèi)存泄漏,一直拖著不肯改進(jìn),這且不說(shuō)。Go語(yǔ)言垃圾回收器真正致命的缺陷是,會(huì)導(dǎo)致整個(gè)進(jìn)程不可預(yù)知的間歇性停頓。像某些大型后臺(tái)服務(wù)程序,如游戲服務(wù)器、APP容器等,由于占用內(nèi)存巨大,其內(nèi)存對(duì)象數(shù)量極多,GC完成一次回收周期,可能需要數(shù)秒甚至更長(zhǎng)時(shí)間,這段時(shí)間內(nèi),整個(gè)服務(wù)進(jìn)程是阻塞的、停頓的,在外界看來(lái)就是服務(wù)中斷、無(wú)響應(yīng),再牛逼的并發(fā)機(jī)制到了這里統(tǒng)統(tǒng)失效。垃圾回收器定期啟動(dòng),每次啟動(dòng)就導(dǎo)致短暫的服務(wù)中斷,這樣下去,還有人敢用嗎?這可是后臺(tái)服務(wù)器進(jìn)程,是Go語(yǔ)言的重點(diǎn)應(yīng)用領(lǐng)域。以上現(xiàn)象可不是我假設(shè)出來(lái)的,而是事實(shí)存在的現(xiàn)實(shí)問(wèn)題,受其嚴(yán)重困擾的也不是一家兩家了(2013年底ECUG Con 2013,京東的劉奇提到了Go語(yǔ)言的GC、defer、標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)是性能殺手,最大的痛苦是GC;美團(tuán)的沈鋒也提到Go語(yǔ)言的GC導(dǎo)致后臺(tái)服務(wù)間隔性停頓是最大的問(wèn)題。更早的網(wǎng)絡(luò)游戲仙俠道開(kāi)發(fā)團(tuán)隊(duì)也曾受Go垃圾回收的沉重打擊)。在實(shí)踐中,你必須努力減少進(jìn)程中的對(duì)象數(shù)量,以便把GC導(dǎo)致的間歇性停頓控制在可接受范圍內(nèi)。除此之外你別無(wú)選擇(難道你還想自己更換GC算法、甚至砍掉GC?那還是Go語(yǔ)言嗎?)。跳出圈外,我近期一直在思考,一定需要垃圾回收器嗎?沒(méi)有垃圾回收器就一定是歷史的倒退嗎?(可能會(huì)新寫(xiě)一篇博客文章專題探討。)
這是說(shuō)的是32位系統(tǒng), 這絕對(duì)不是Go語(yǔ)言的重點(diǎn)應(yīng)用領(lǐng)域!! 我可以說(shuō)Go出生就是面向64位系統(tǒng)和多核心CPU環(huán)境設(shè)計(jì)的. (再說(shuō) Rust 目前好像還不支持 XP 吧, 這可不可以算是影響巨大?)
32位當(dāng)時(shí)是有問(wèn)題, 但是對(duì)實(shí)際生產(chǎn)影響并不大(請(qǐng)問(wèn)樓主還是在用32位系統(tǒng)嗎, 還只安裝4GB的內(nèi)存嗎). 如果是8位單片機(jī)環(huán)境, 建議就不要用Go語(yǔ)言了, 直接C語(yǔ)言好了.
而且這個(gè)問(wèn)題早就不存在了(大家可以去看Go的發(fā)布日志).
Go的出生也就5年時(shí)間, GC的完善和改進(jìn)是一個(gè)持續(xù)的工作, 2015年8月將發(fā)布的 Go1.5將采用并行GC.
關(guān)于GC的被人詬病的地方是會(huì)導(dǎo)致卡頓, 但是我以為這個(gè)主要是因?yàn)镚C的實(shí)現(xiàn)還不夠完美而導(dǎo)致的.
如果是完美的并發(fā)和增量的GC, 那應(yīng)該不會(huì)出現(xiàn)大的卡頓問(wèn)題的.
當(dāng)然, 如果非要實(shí)時(shí)性, 那用C好了(實(shí)時(shí)并不表示性能高, 只是響應(yīng)時(shí)間可控).
對(duì)于Rust之類沒(méi)有GC的語(yǔ)言來(lái)說(shuō), 想很方便的開(kāi)發(fā)并發(fā)的后臺(tái)程序那幾乎是不可能的.
不要總是吹Rust能代替底層/中層/上層的開(kāi)發(fā), 我們要看有誰(shuí)用Rust真的做了什么.
1.6 禁止未使用變量和多余import
Go編譯器不允許存在被未被使用的變量和多余的import,如果存在,必然導(dǎo)致編譯錯(cuò)誤。但是現(xiàn)實(shí)情況是,在代碼編寫(xiě)、重構(gòu)、調(diào)試過(guò)程中,例如,臨時(shí)性的注釋掉一行代碼,很容易就會(huì)導(dǎo)致同時(shí)出現(xiàn)未使用的變量和多余的import,直接編譯錯(cuò)誤了,你必須相應(yīng)的把變量定義注釋掉,再翻頁(yè)回到文件首部把多余的import也注釋掉,……等事情辦完了,想把剛才注釋的代碼找回來(lái),又要好幾個(gè)麻煩的步驟。還有一個(gè)讓人蛋疼的問(wèn)題,編寫(xiě)數(shù)據(jù)庫(kù)相關(guān)的代碼時(shí),如果你import某數(shù)據(jù)庫(kù)驅(qū)動(dòng)的pkg,它編譯給你報(bào)錯(cuò),說(shuō)不需要import這個(gè)未被使用的pkg;但如果你聽(tīng)信編譯器的話刪掉該import,編譯是通過(guò)了,運(yùn)行時(shí)必然報(bào)錯(cuò),說(shuō)找不到數(shù)據(jù)庫(kù)驅(qū)動(dòng);你看看程序員被折騰的兩邊不是人,最后不得不請(qǐng)出大神:import _。對(duì)待這種問(wèn)題,一個(gè)比較好的解決方案是,視其為編譯警告而非編譯錯(cuò)誤。但是Go語(yǔ)言開(kāi)發(fā)者很固執(zhí),不容許這種折中方案。
這個(gè)問(wèn)題我只能說(shuō)樓主的吐槽真的是沒(méi)水平.
為何不使用的是錯(cuò)誤而不是警告? 這是為了將低級(jí)的bug消滅在編譯階段(大家可以想下C/C++的那么多警告有什么卵用).
而且, import 即使沒(méi)有使用的話, 也是用副作用的, 因?yàn)?import 會(huì)導(dǎo)致 init 和全局變量的初始化.
如果某些代碼沒(méi)有使用, 為何要執(zhí)行 init 這些初始化呢?
如果是因?yàn)檎{(diào)試而添加的變量, 那么調(diào)試完刪除不是很正常的要求嗎?
如果是因?yàn)檎{(diào)試而要導(dǎo)入fmt或log之類的包, 刪除調(diào)試代碼后又導(dǎo)致 import 錯(cuò)誤的花,
樓主難道不知道在一個(gè)獨(dú)立的文件包裝下類似的輔助調(diào)試的函數(shù)嗎?
import (
"fmt"
"log"
)
func logf(format string, a ...interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt.Fprintf(os.Stderr, format, a...)
}
func fatalf(format string, a ...interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt.Fprintf(os.Stderr, format, a...)
os.Exit(1)
}
import _ 是有明確行為的用法, 就是為了執(zhí)行包中的 init 等函數(shù)(可以做某些注冊(cè)操作).
將警告當(dāng)作錯(cuò)誤是Go的一個(gè)哲學(xué), 當(dāng)然在樓主看來(lái)這是白癡做法.
1.7 創(chuàng)建對(duì)象的方式太多令人糾結(jié)
創(chuàng)建對(duì)象的方式,調(diào)用new函數(shù)、調(diào)用make函數(shù)、調(diào)用New方法、使用花括號(hào)語(yǔ)法直接初始化結(jié)構(gòu)體,你選哪一種?不好選擇,因?yàn)闆](méi)有一個(gè)固定的模式。從實(shí)踐中看,如果要?jiǎng)?chuàng)建一個(gè)語(yǔ)言內(nèi)置類型(如channel、map)的對(duì)象,通常用make函數(shù)創(chuàng)建;如果要?jiǎng)?chuàng)建標(biāo)準(zhǔn)庫(kù)或第三方庫(kù)定義的類型的對(duì)象,首先要去文檔里找一下有沒(méi)有New方法,如果有就最好調(diào)用New方法創(chuàng)建對(duì)象,如果沒(méi)有New方法,則退而求其次,用初始化結(jié)構(gòu)體的方式創(chuàng)建其對(duì)象。這個(gè)過(guò)程頗為周折,不像C++、Java、C#那樣直接new就行了。
C++的new是狗屎. new導(dǎo)致的問(wèn)題是構(gòu)造函數(shù)和普通函數(shù)的行為不一致, 這個(gè)補(bǔ)丁特性真的沒(méi)啥優(yōu)越的.
我還是喜歡C語(yǔ)言的 fopen 和 malloc 之類構(gòu)造函數(shù), 構(gòu)造函數(shù)就是普通函數(shù), Go語(yǔ)言中也是這樣.
C++中, 除了構(gòu)造不兼容普通函數(shù), 析構(gòu)函數(shù)也是不兼容普通函數(shù). 這個(gè)而引入的坑有很多吧.
1.8 對(duì)象沒(méi)有構(gòu)造函數(shù)和析構(gòu)函數(shù)
沒(méi)有構(gòu)造函數(shù)還好說(shuō),畢竟還有自定義的New方法,大致也算是構(gòu)造函數(shù)了。沒(méi)有析構(gòu)函數(shù)就比較難受了,沒(méi)法實(shí)現(xiàn)RAII。額外的人工處理資源清理工作,無(wú)疑加重了程序員的心智負(fù)擔(dān)。沒(méi)人性啊,還嫌我們程序員加班還少嗎?C++里有析構(gòu)函數(shù),Java里雖然沒(méi)有析構(gòu)函數(shù)但是有人家finally語(yǔ)句啊,Go呢,什么都沒(méi)有。沒(méi)錯(cuò),你有個(gè)defer,可是那個(gè)defer問(wèn)題更大,詳見(jiàn)下文吧。
defer 可以覆蓋析構(gòu)函數(shù)的行為, 當(dāng)然 defer 還有其他的任務(wù). Swift2.0 也引入了一個(gè)簡(jiǎn)化版的 defer 特性.
1.9 defer語(yǔ)句的語(yǔ)義設(shè)定不甚合理
Go語(yǔ)言設(shè)計(jì)defer語(yǔ)句的出發(fā)點(diǎn)是好的,把釋放資源的“代碼”放在靠近創(chuàng)建資源的地方,但把釋放資源的“動(dòng)作”推遲(defer)到函數(shù)返回前執(zhí)行。遺憾的是其執(zhí)行時(shí)機(jī)的設(shè)置似乎有些不甚合理。設(shè)想有一個(gè)需要長(zhǎng)期運(yùn)行的函數(shù),其中有無(wú)限循環(huán)語(yǔ)句,在循環(huán)體內(nèi)不斷的創(chuàng)建資源(或分配內(nèi)存),并用defer語(yǔ)句確保釋放。由于函數(shù)一直運(yùn)行沒(méi)有返回,所有defer語(yǔ)句都得不到執(zhí)行,循環(huán)過(guò)程中創(chuàng)建的大量短暫性資源一直積累著,得不到回收。而且,系統(tǒng)為了存儲(chǔ)defer列表還要額外占用資源,也是持續(xù)增加的。這樣下去,過(guò)不了多久,整個(gè)系統(tǒng)就要因?yàn)橘Y源耗盡而崩潰。像這類長(zhǎng)期運(yùn)行的函數(shù),http.ListenAndServe()就是典型的例子。在Go語(yǔ)言重點(diǎn)應(yīng)用領(lǐng)域,可以說(shuō)幾乎每一個(gè)后臺(tái)服務(wù)程序都必然有這么一類函數(shù),往往還都是程序的核心部分。如果程序員不小心在這些函數(shù)中使用了defer語(yǔ)句,可以說(shuō)后患無(wú)窮。如果語(yǔ)言設(shè)計(jì)者把defer的語(yǔ)義設(shè)定為在所屬代碼塊結(jié)束時(shí)(而非函數(shù)返回時(shí))執(zhí)行,是不是更好一點(diǎn)呢?可是Go 1.0早已發(fā)布定型,為了保持向后兼容性,已經(jīng)不可能改變了。小心使用defer語(yǔ)句!一不小心就中招。
前面說(shuō)到 defer 還有其他的任務(wù), 也就是 defer 中執(zhí)行的 recover 可以捕獲 panic 拋出的異常.
還有 defer 可以在 return 之后修改命名的返回值.
上面2個(gè)工作要求 defer 只能在函數(shù)退出時(shí)來(lái)執(zhí)行.
樓主說(shuō)的 defer 是類似 Swift2.0 中 defer 的行為, 但是 Swift2.0 中 defer 是沒(méi)有前面2個(gè)特性的.
Go中的defer是以函數(shù)作用域作為觸發(fā)的條件的, 是會(huì)導(dǎo)致樓主說(shuō)的在 for 中執(zhí)行的錯(cuò)誤用法(哪個(gè)語(yǔ)言沒(méi)有坑呢?).
不過(guò) for 中 局部 defer 也是有辦法的 (Go中的defer是以函數(shù)作用域):
for {
func(){
f, err := os.Open(...)
defer f.Close()
}()
}
在 for 中做一個(gè)閉包函數(shù)就可以了. 自己不會(huì)用不要怪別人沒(méi)告訴你.
1.10 許多語(yǔ)言內(nèi)置設(shè)施不支持用戶定義的類型
for in、make、range、channel、map等都僅支持語(yǔ)言內(nèi)置類型,不支持用戶定義的類型(?)。用戶定義的類型沒(méi)法支持for in循環(huán),用戶不能編寫(xiě)像make、range那樣“參數(shù)類型和個(gè)數(shù)”甚至“返回值類型和個(gè)數(shù)”都可變的函數(shù),不能編寫(xiě)像channel、map那樣類似泛型的數(shù)據(jù)類型。語(yǔ)言內(nèi)置的那些東西,處處充斥著斧鑿的痕跡。這體現(xiàn)了語(yǔ)言設(shè)計(jì)的局限性、封閉性、不完善,可擴(kuò)展性差,像是新手作品——且不論其設(shè)計(jì)者和實(shí)現(xiàn)者如何權(quán)威。延伸閱讀:Go語(yǔ)言是30年前的陳舊設(shè)計(jì)思想,用戶定義的東西幾乎都是二等公民(Tikhon Jelvis)。
說(shuō)到底, 這個(gè)是因?yàn)閷?duì)泛型支持的不完備導(dǎo)致的.
Go語(yǔ)言是沒(méi)啥NB的特性, 但是Go的特性和工具組合在一起就是好用.
這就是Go語(yǔ)言NB的地方.
1.11 沒(méi)有泛型支持,常見(jiàn)數(shù)據(jù)類型接口丑陋
沒(méi)有泛型的話,List、Set、Tree這些常見(jiàn)的基礎(chǔ)性數(shù)據(jù)類型的接口就只能很丑陋:放進(jìn)去的對(duì)象是一個(gè)具體的類型,取出來(lái)之后成了無(wú)類型的interface{}(可以視為所有類型的基礎(chǔ)類型),還得強(qiáng)制類型轉(zhuǎn)換之后才能繼續(xù)使用,令人無(wú)語(yǔ)。Go語(yǔ)言缺少min、max這類函數(shù),求數(shù)值絕對(duì)值的函數(shù)abs只接收/返回雙精度小數(shù)類型,排序接口只能借助sort.Interface無(wú)奈的回避了被比較對(duì)象的類型,等等等等,都是沒(méi)有泛型導(dǎo)致的結(jié)果。沒(méi)有泛型,接口很難優(yōu)雅起來(lái)。Go開(kāi)發(fā)者沒(méi)有明確拒絕泛型,只是說(shuō)還沒(méi)有找到很好的方法實(shí)現(xiàn)泛型(能不能學(xué)學(xué)已經(jīng)開(kāi)源的語(yǔ)言呀)?,F(xiàn)實(shí)是,Go 1.0已經(jīng)定型,泛型還沒(méi)有,那些丑陋的接口為了保持向后兼容必須長(zhǎng)期存在著。
Go有自己的哲學(xué), 如果能有和目前哲學(xué)不沖突的泛型實(shí)現(xiàn), 他們是不會(huì)反對(duì)的.
如果只是簡(jiǎn)單學(xué)學(xué)(或者叫抄襲)已經(jīng)開(kāi)源的語(yǔ)言的語(yǔ)法, 那是C++的設(shè)計(jì)風(fēng)格(或者說(shuō)C++從來(lái)都是這樣設(shè)計(jì)的, 有什么特性就抄什么), 導(dǎo)致了各種腦裂的編程風(fēng)格.
編譯時(shí)泛型和運(yùn)行時(shí)泛型可能是無(wú)法完全兼容的, 看這個(gè)例子:
type AdderT interface {
Add(a, b T) T
}