『每周譯Go』Go 1.18 泛型的一些技巧和困擾
截至2021年11月17日,社區(qū)可能還沒有使用 Go 1.18 泛型功能的緩存庫。
我嘗試在這里實(shí)現(xiàn)了第一個 Go 1.18 泛型的緩存庫。如果你能夠給的 GitHub 加個 Star,我會感到非常高興。https://github.com/Code-Hex/go-generics-cache
在這篇文章中,我將介紹我在開發(fā)這個緩存庫時遇到的關(guān)于 Go 泛型的一些情況,以及我發(fā)現(xiàn)的一些技巧和困擾。
對任何類型都返回零值
你經(jīng)常會寫一些返回 any和 error的代碼,比如說下面這樣。當(dāng)一個函數(shù)發(fā)生錯誤時,你會寫一些返回零值和錯誤的代碼,但現(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è)你在這里寫return 0, err。這將是一個編譯錯誤。原因是any類型可以是int類型以外的類型,比如string類型。那么我們應(yīng)該怎么做呢?
讓我們用類型參數(shù)的V聲明一次變量。然后你可以把它寫成可編譯的形式,如下:
func?Do[V?any](v?V)?(V,?error)?{
????var?ret?V
????if?err?:=?validate(v);?err?!=?nil?{
????????return?ret,?err
????}
????return?v,?nil
}
此外,可以使用帶命名的返回值來簡化單行的書寫。
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
不要試圖用約束做類型轉(zhuǎn)換
我想提供兩個方法,Increment和Decrement。它們可以從go-generics-cache(https://github.com/Code-Hex/go-generics-cache)庫中增加或減少值,如果存儲的值滿足Number約束(https://github.com/Code-Hex/go-generics-cache/blob/d5c3dda0e57b4c533c1e744869032c33a4fc2d9e/constraint.go#L5-L8)。
讓我們用Increment方法作為一個例子。我最初寫的代碼是這樣的:
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的類型來匹配被滿足的約束。如果滿足Number約束,這個方法就會增加,否則什么都不做。
這將不會被編譯。
Go 不為約束條件提供條件分支 約束是一個接口,Go 不允許使用接口進(jìn)行類型斷言 n的類型沒有確定,所以+操作是不可能的首先,不能保證 items的類型與n的類型相同
為了解決這些問題,我決定嵌入Cache結(jié)構(gòu)。我還定義了一個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)的值的類型永遠(yuǎn)是Number的約束。所以我們可以給NumberCache結(jié)構(gòu)添加一個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)
讓我們再看一下Cache結(jié)構(gòu)的定義。
type?Cache[K?comparable,?V?any]?struct?{
????items?map[K]V
}
Go 范型被定義為一種帶有約束的語言規(guī)范,這種約束被稱為 comparable。這允許只有類型可以使用 == 和 !=。
我覺得這個約束條件讓我很困擾。讓我解釋一下困擾我的原因。
我定義了一個函數(shù)來比較兩個 comparable 的值。
func?Equal[T?comparable](v1,?v2?T)?bool?{
????return?v1?==?v2
}
只允許 comparable 的類型,如果在編譯時將不可比較的類型傳遞給函數(shù),就會導(dǎo)致錯誤。你可能認(rèn)為這很有用。
然而,根據(jù) Go 的規(guī)范,interface{}也滿足這個可比較的約束。
如果interface{}可以被滿足,下面的代碼就可以被編譯了。
func?main()?{
????v1?:=?interface{}(func()?{})
????v2?:=?interface{}(func()?{})
????Equal(v1,?v2)
}
這表明func()類型是一個不可比較的類型。但可以通過將其轉(zhuǎn)換為interface{}類型來轉(zhuǎn)換為可比較的類型。
interface{}類型只有在運(yùn)行時才能知道它是否是一個可比較的類型。
如果這是一段復(fù)雜的代碼,可能很難被注意到。
https://gotipplay.golang.org/p/tbKKuehbzUv
我相信我們需要另一個不接受interface{}的可比約束,以便在編譯時注意到。
這種約束可以由 Go 用戶來定義嗎?目前的答案是不能。
這是因為comparable約束包含 "可比較的結(jié)構(gòu)體" 和 "可比較的數(shù)組"。
這些約束目前不能由 Go 用戶定義。因此,我想把它們作為 Go 規(guī)范來提供。
我還為此創(chuàng)建了一個提案,如果你也認(rè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 的更多資訊,還可以通過掃描的方式,進(jìn)群一起探討哦~
