『每周譯Go』Go 1.18 泛型的一些技巧和困擾
截至2021年11月17日,社區(qū)可能還沒(méi)有使用 Go 1.18 泛型功能的緩存庫(kù)。
我嘗試在這里實(shí)現(xiàn)了第一個(gè) Go 1.18 泛型的緩存庫(kù)。如果你能夠給的 GitHub 加個(gè) Star,我會(huì)感到非常高興。https://github.com/Code-Hex/go-generics-cache
在這篇文章中,我將介紹我在開(kāi)發(fā)這個(gè)緩存庫(kù)時(shí)遇到的關(guān)于 Go 泛型的一些情況,以及我發(fā)現(xiàn)的一些技巧和困擾。
對(duì)任何類(lèi)型都返回零值
你經(jīng)常會(huì)寫(xiě)一些返回 any和 error的代碼,比如說(shuō)下面這樣。當(dāng)一個(gè)函數(shù)發(fā)生錯(cuò)誤時(shí),你會(huì)寫(xiě)一些返回零值和錯(cuò)誤的代碼,但現(xiàn)在你需要換一種思維方式。
func?Do[V?any](v?V)?(V,?error)?{
????if?err?:=?validate(v);?err?!=?nil?{
????????//?What?should?we?return?here?
????}
????return?v,?nil
}
func?validate[V?any](v?V)?error
假設(shè)你在這里寫(xiě)return 0, err。這將是一個(gè)編譯錯(cuò)誤。原因是any類(lèi)型可以是int類(lèi)型以外的類(lèi)型,比如string類(lèi)型。那么我們應(yīng)該怎么做呢?
讓我們用類(lèi)型參數(shù)的V聲明一次變量。然后你可以把它寫(xiě)成可編譯的形式,如下:
func?Do[V?any](v?V)?(V,?error)?{
????var?ret?V
????if?err?:=?validate(v);?err?!=?nil?{
????????return?ret,?err
????}
????return?v,?nil
}
此外,可以使用帶命名的返回值來(lái)簡(jiǎn)化單行的書(shū)寫(xiě)。
func?Do[V?any](v?V)?(ret?V,?_?error)?{
????if?err?:=?validate(v);?err?!=?nil?{
????????return?ret,?err
????}
????return?v,?nil
}
https://gotipplay.golang.org/p/0UqA0PIO9X8
不要試圖用約束做類(lèi)型轉(zhuǎn)換
我想提供兩個(gè)方法,Increment和Decrement。它們可以從go-generics-cache(https://github.com/Code-Hex/go-generics-cache)庫(kù)中增加或減少值,如果存儲(chǔ)的值滿(mǎn)足Number約束(https://github.com/Code-Hex/go-generics-cache/blob/d5c3dda0e57b4c533c1e744869032c33a4fc2d9e/constraint.go#L5-L8)。
讓我們用Increment方法作為一個(gè)例子。我最初寫(xiě)的代碼是這樣的:
type?Cache[K?comparable,?V?any]?struct?{
????items?map[K]V
}
func?(c?*Cache[K,?V])?Increment(k?K,?n?V)?(val?V,?_?error)?{
????got,?ok?:=?c.items[k]
????if?!ok?{
????????return?val,?errors.New("not?found")
????}
????switch?(interface{})(n).(type)?{
????case?Number:
????????nv?:=?got?+?n
????????c.items[k]?=?nv
????????return?nv,?nil
????}
????return?val,?nil
}
我在考慮使用值n V的類(lèi)型來(lái)匹配被滿(mǎn)足的約束。如果滿(mǎn)足Number約束,這個(gè)方法就會(huì)增加,否則什么都不做。
這將不會(huì)被編譯。
Go 不為約束條件提供條件分支 約束是一個(gè)接口,Go 不允許使用接口進(jìn)行類(lèi)型斷言 n的類(lèi)型沒(méi)有確定,所以+操作是不可能的首先,不能保證 items的類(lèi)型與n的類(lèi)型相同
為了解決這些問(wèn)題,我決定嵌入Cache結(jié)構(gòu)。我還定義了一個(gè)NumberCache結(jié)構(gòu),可以一直處理Number約束。
繼承 Cache結(jié)構(gòu)體所持有的字段數(shù)據(jù)處理 Cache的方法
type?NumberCache[K?comparable,?V?Number]?struct?{
????*Cache[K,?V]
}
這樣,我們可以保證傳遞給Cache結(jié)構(gòu)的值的類(lèi)型永遠(yuǎn)是Number的約束。所以我們可以給NumberCache結(jié)構(gòu)添加一個(gè)Increment方法。
func?(c?*NumberCache[K,?V])?Increment(k?K,?n?V)?(val?V,?_?error)?{
????got,?ok?:=?c.Cache.items[k]
????if?!ok?{
????????return?val,?errors.New("not?found")
????}
????nv?:=?got?+?n
????c.Cache.items[k]?=?nv
????return?val,?nil
}
https://gotipplay.golang.org/p/poQeWw4UE_L
使我困擾的點(diǎn)
讓我們?cè)倏匆幌?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Cache結(jié)構(gòu)的定義。
type?Cache[K?comparable,?V?any]?struct?{
????items?map[K]V
}
Go 范型被定義為一種帶有約束的語(yǔ)言規(guī)范,這種約束被稱(chēng)為 comparable。這允許只有類(lèi)型可以使用 == 和 !=。
我覺(jué)得這個(gè)約束條件讓我很困擾。讓我解釋一下困擾我的原因。
我定義了一個(gè)函數(shù)來(lái)比較兩個(gè) comparable 的值。
func?Equal[T?comparable](v1,?v2?T)?bool?{
????return?v1?==?v2
}
只允許 comparable 的類(lèi)型,如果在編譯時(shí)將不可比較的類(lèi)型傳遞給函數(shù),就會(huì)導(dǎo)致錯(cuò)誤。你可能認(rèn)為這很有用。
然而,根據(jù) Go 的規(guī)范,interface{}也滿(mǎn)足這個(gè)可比較的約束。
如果interface{}可以被滿(mǎn)足,下面的代碼就可以被編譯了。
func?main()?{
????v1?:=?interface{}(func()?{})
????v2?:=?interface{}(func()?{})
????Equal(v1,?v2)
}
這表明func()類(lèi)型是一個(gè)不可比較的類(lèi)型。但可以通過(guò)將其轉(zhuǎn)換為interface{}類(lèi)型來(lái)轉(zhuǎn)換為可比較的類(lèi)型。
interface{}類(lèi)型只有在運(yùn)行時(shí)才能知道它是否是一個(gè)可比較的類(lèi)型。
如果這是一段復(fù)雜的代碼,可能很難被注意到。
https://gotipplay.golang.org/p/tbKKuehbzUv
我相信我們需要另一個(gè)不接受interface{}的可比約束,以便在編譯時(shí)注意到。
這種約束可以由 Go 用戶(hù)來(lái)定義嗎?目前的答案是不能。
這是因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">comparable約束包含 "可比較的結(jié)構(gòu)體" 和 "可比較的數(shù)組"。
這些約束目前不能由 Go 用戶(hù)定義。因此,我想把它們作為 Go 規(guī)范來(lái)提供。
我還為此創(chuàng)建了一個(gè)提案,如果你也認(rèn)同這個(gè)說(shuō)法,請(qǐng)?jiān)?GitHub issue 上給我??,我將不勝感激。https://github.com/golang/go/issues/49587
文中提到的鏈接
comparable https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#comparable-types-in-constraints
原文信息
原文地址:https://dev.to/codehex/some-tips-and-bothers-for-go-118-generics-lc7
原文作者:Kei Kamikawa
本文永久鏈接:https://github.com/gocn/translator/blob/master/2021/w46_some-tips-and-bothers-for-go-118-generics.md
譯者:Cluas
想要了解關(guān)于 Go 的更多資訊,還可以通過(guò)掃描的方式,進(jìn)群一起探討哦~
