Go:語法糖的代價(jià)
點(diǎn)擊上方“Go編程時(shí)光”,選擇“加為星標(biāo)”
第一時(shí)間關(guān)注Go技術(shù)干貨!

本文是 Go語言中文網(wǎng)組織的 GCTT 翻譯,發(fā)布在 Go語言中文網(wǎng)公眾號(hào),轉(zhuǎn)載請前往聯(lián)系授權(quán)。
在 Go 語言中,你可以用少量的代碼表達(dá)很多東西。您通常可以查看一小段代碼并清楚地了解此程序的功能。這在 Go 社區(qū)中被稱為地道的 Go 代碼。保持跨項(xiàng)目代碼的一致性需要持續(xù)不斷地努力。
當(dāng)我遇到 Go 的部分看起來不像地道 Go 代碼時(shí),這通常是有原因的。最近,我注意到 Go 中的接口切片(或抽象數(shù)組)工作方式有點(diǎn)怪異。這種怪異有助于理解在 Go 中使用復(fù)雜類型會(huì)帶來一些成本,而且這些語法糖[1]并不總是沒有代價(jià)的。為深入了解問題出現(xiàn)的原因,我對遇到的行為進(jìn)行拆分,這助于闡明 Go 的一些設(shè)計(jì)原則。
# 舉例說明
我們將編寫一個(gè)小型程序,它定義一個(gè)動(dòng)物列表(例如,dogs),并調(diào)用一個(gè)函數(shù),將每個(gè)動(dòng)物的噪聲輸出到控制臺(tái)
animals := []Animal{Dog{}}
PrintNoises(animals)
程序成功通過編譯,并在控制臺(tái)打印出了“ Woof!”。下面就是這個(gè)程序的類似的版本:
dogs := []Dog{Dog{}}
PrintNoises(dogs)
程序無法編譯,并將以下錯(cuò)誤打印到控制臺(tái),而不是輸出 "Woof!"
cannot use dogs (type []Dog) as type []Animal in argument to PrintNoises
如果你熟悉 Go,你可能會(huì)認(rèn)為應(yīng)該檢查一下 Dog 實(shí)現(xiàn)了 Animal,對吧? 如果是實(shí)現(xiàn)錯(cuò)誤,它的輸出應(yīng)該類似于
cannot use dogs (type []Dog) as type []Animal in argument to PrintNoises: []Dog does not implement []Animal (missing Noise method)
為什么第一個(gè)程序可以用 Dog 作為 Animal 來編譯和運(yùn)行,而第二個(gè)程序卻不能,即使它們看起來都是地道的和正確的
下面是本例中用作參考的其余代碼。你可以通過編譯它,來了解上述用法的內(nèi)部原理
type Animal interface {
Noise() string
}
type Dog struct{}
func (Dog) Noise() string {
return "Woof!"
}
func PrintNoises(as []Animal) {
for _, a := range as {
fmt.Println(a.Noise())
}
}
# 進(jìn)一步簡化問題
讓我們試著用一種更簡單的方法來復(fù)現(xiàn)這個(gè)問題,以便更好地理解它。靜態(tài)類型檢查是一種有用的 Go pattern,用于斷言類型是否實(shí)現(xiàn)了接口。讓我們先檢查一下 Dog 是否實(shí)現(xiàn)了 Animal
var _ Animal = Dog{}
上面代碼編譯成功。那我們接下來就檢查程序中用到的 slices
var _ []Animal = []Dog{}
上面代碼沒有編譯成功,編譯器報(bào)錯(cuò):
cannot use []Dog literal (type []Dog) as type []Animal in assignment
現(xiàn)在,我們已經(jīng)復(fù)現(xiàn)了一個(gè)與例子類似(但不是完全相同)的錯(cuò)誤。利用這些不同的線索,我做了一些研究來找出如何解決這個(gè)問題,以及為什么會(huì)發(fā)生這樣的事情
# 尋找解決方案
在做了一些研究之后,我發(fā)現(xiàn)了兩件事:一個(gè)是解決方案,另一個(gè)是背后的原理。我們從修正程序開始,因?yàn)樗兄谡f明基本原理
下面是最初未能編譯的代碼的一個(gè)修復(fù):
dogs := []Dog{Dog{}}
// 新邏輯: 把 dogs 的切片轉(zhuǎn)換成 animals 的切片
animals := []Animal{}
for _, d := range dogs {
animals = append(animals, Animal(d))
}
PrintNoises(animals)
通過將 Dog 的切片轉(zhuǎn)換為 Animal 的切片,現(xiàn)在可以將其傳入 Printnoise 函數(shù)并成功運(yùn)行。當(dāng)然,這看起來有點(diǎn)傻,因?yàn)樗旧鲜且呀?jīng)運(yùn)行的第一個(gè)程序的冗長版本。然而,在一個(gè)更大的項(xiàng)目中,這一點(diǎn)可能并那么明顯。修復(fù)的代價(jià)是多了四行代碼。這四行代碼似乎是多余,直到你開始考慮作為開發(fā)人員必須修復(fù)它的原因
# 尋找原理
現(xiàn)在你知道如何修復(fù)它,我們來探究它背后的原理。我找到了不錯(cuò)的解析:go 不支持切片中協(xié)變[2](譯者注: 協(xié)變[3]: 原文單詞為 covariance, 是指在計(jì)算機(jī)科學(xué)中,描述具有父/子型別關(guān)系的多個(gè)型別通過型別構(gòu)造器、構(gòu)造出的多個(gè)復(fù)雜型別之間是否有父/子型別關(guān)系的用語)
換句話說,Go 不會(huì)執(zhí)行導(dǎo)致 O(N) 線性操作的類型轉(zhuǎn)換(如切片的情況),而是將責(zé)任委托給開發(fā)人員。也就是說,執(zhí)行這種類型的轉(zhuǎn)換是有成本的。不過,Go 并不是每次都這樣做。例如,當(dāng)將字符串轉(zhuǎn)換為 []byte 節(jié)時(shí),Go 將免費(fèi)為您執(zhí)行這種線性轉(zhuǎn)換,這可能是因?yàn)檫@種轉(zhuǎn)換通常很方便。這只是語言中語法糖的眾多例子之一。對于切片(和其他非基本類型),Go 選擇不為您承擔(dān)執(zhí)行此操作的額外成本
這是有道理的——在我使用 Go 的 3 年里,這是我第一次遇到這種情況。這可能是因?yàn)?Go 在語法中灌輸了“simpler is better”的思想
# 結(jié)論
一門語言的作者通常會(huì)在語法糖方面做出權(quán)衡,有時(shí)他們會(huì)添加功能,即便這會(huì)使語言變得更加臃腫,有時(shí)他們會(huì)將成本轉(zhuǎn)嫁給開發(fā)人員。我認(rèn)為,不隱式地執(zhí)行高開銷的操作的決定在保持 Go 語言地道、整潔、可控上有積極的影響。
上面的例子只是這個(gè)道理的一個(gè)應(yīng)用。這個(gè)例子表明,熟悉一種語言的習(xí)慣用法有副作用。對設(shè)計(jì)決策保持深思熟慮總是一個(gè)好主意,而不是期望語言或編譯器能幫到你。
我鼓勵(lì)您在 Go 中尋找更多存在權(quán)衡語法的地方。它能幫助你更好地理解這門語言。我亦如此。
# 引用
以下是本文的引用:
GitHub Gist of the above example[4]
Go 語言官網(wǎng)[5]
Thread on covariance in Go[6]
Big-O notation[7]
Syntactic sugar[8]
via: https://medium.com/@asilvr/the-cost-of-syntactic-sugar-in-go-5aa9dc307fe0
作者:Katy Slemon[9]譯者:Alex1996a[10]校對:lxbwolf[11]
參考資料
[1]
語法糖: https://en.wikipedia.org/wiki/Syntactic_sugar
[2]go 不支持切片中協(xié)變: https://www.reddit.com/r/golang/comments/3gtg3i/passing_slice_of_values_as_slice_of_interfaces/
[3]協(xié)變: https://zh.wikipedia.org/wiki/%E5%8D%8F%E5%8F%98%E4%B8%8E%E9%80%86%E5%8F%98
[4]GitHub Gist of the above example: https://gist.github.com/asilvr/4d4da3cdc8180c5a9740d2890d833923
[5]Go 語言官網(wǎng): https://golang.org
[6]Thread on covariance in Go: https://www.reddit.com/r/golang/comments/3gtg3i/passing_slice_of_values_as_slice_of_interfaces/
[7]Big-O notation: https://en.wikipedia.org/wiki/Big_O_notation
[8]Syntactic sugar: https://en.wikipedia.org/wiki/Syntactic_sugar
[9]Katy Slemon: https://medium.com/@katyslemon
[10]Alex1996a: https://github.com/Alex1996a
[11]lxbwolf: https://github.com/lxbwolf
[12]GCTT: https://github.com/studygolang/GCTT
[13]Go 中文網(wǎng): https://studygolang.com/

???
