Go 1.18 泛型的一些技巧和困擾
- 原文地址: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
截至2021年11月17日,社群可能還沒有使用 Go 1.18 泛型功能的快取庫。
我嘗試在這裡實現了第一個 Go 1.18 泛型的快取庫。如果你能夠給的 GitHub 加個 Star,我會感到非常高興。 https://github.com/Code-Hex/go-generics-cache
在這篇文章中,我將介紹我在開發這個快取庫時遇到的關於 Go 泛型的一些情況,以及我發現的一些技巧和困擾。
對任何型別都返回零值
你經常會寫一些返回 any
和 error
的程式碼,比如說下面這樣。當一個函式發生錯誤時,你會寫一些返回零值和錯誤的程式碼,但現在你需要換一種思維方式。
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
假設你在這裡寫return 0, err
。這將是一個編譯錯誤。原因是any
型別可以是int
型別以外的型別,比如string
型別。那麼我們應該怎麼做呢?
讓我們用型別引數的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
不要試圖用約束
做型別轉換
我想提供兩個方法,Increment
和Decrement
。它們可以從go-generics-cache庫中增加或減少值,如果儲存的值滿足Number 約束。
讓我們用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 不允許使用介面進行型別斷言
-
n
的型別沒有確定,所以+
操作是不可能的 - 首先,不能保證
items
的型別與n
的型別相同
為了解決這些問題,我決定嵌入Cache
結構。我還定義了一個NumberCache
結構,可以一直處理Number
約束。
- 繼承
Cache
結構體所持有的欄位資料 - 處理
Cache
的方法
type NumberCache[K comparable, V Number] struct {
*Cache[K, V]
}
這樣,我們可以保證傳遞給Cache
結構的值的型別永遠是Number
的約束。所以我們可以給NumberCache
結構新增一個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
使我困擾的點
讓我們再看一下Cache
結構的定義。
type Cache[K comparable, V any] struct {
items map[K]V
}
Go 範型被定義為一種帶有約束的語言規範,這種約束被稱為 comparable。這允許只有型別可以使用 ==
和 !=
。
我覺得這個約束條件讓我很困擾。讓我解釋一下困擾我的原因。
我定義了一個函式來比較兩個 comparable
的值。
func Equal[T comparable](v1, v2 T) bool {
return v1 == v2
}
只允許 comparable
的型別,如果在編譯時將不可比較的型別傳遞給函式,就會導致錯誤。你可能認為這很有用。
然而,根據 Go 的規範,interface{}
也滿足這個可比較的約束。
如果interface{}
可以被滿足,下面的程式碼就可以被編譯了。
func main() {
v1 := interface{}(func() {})
v2 := interface{}(func() {})
Equal(v1, v2)
}
這表明func()
型別是一個不可比較的型別。但可以通過將其轉換為interface{}
型別來轉換為可比較的型別。
interface{}
型別只有在執行時才能知道它是否是一個可比較的型別。
如果這是一段複雜的程式碼,可能很難被注意到。
https://gotipplay.golang.org/p/tbKKuehbzUv
我相信我們需要另一個不接受interface{}
的可比約束,以便在編譯時注意到。
這種約束可以由 Go 使用者來定義嗎?目前的答案是不能。
這是因為comparable
約束包含 "可比較的結構體" 和 "可比較的陣列"。
這些約束目前不能由 Go 使用者定義。因此,我想把它們作為 Go 規範來提供。
我還為此建立了一個提案,如果你也認同這個說法,請在 GitHub issue 上給我?,我將不勝感激。 https://github.com/golang/go/issues/49587
文中提到的連結
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Go 1.18泛型的侷限性初探Go泛型
- go 1.18 泛型初體驗Go泛型
- 支援泛型的Go語言1.18釋出泛型Go
- go1.18泛型的簡單嘗試Go泛型
- Go 1.18 泛型全面講解:一篇講清泛型的全部Go泛型
- 預計在 Go 1.18 中內建泛型Go泛型
- Redux 的困擾與如何技術選型Redux
- Go 泛型Go泛型
- 使用copy命令解決LONG型別的困擾型別
- Go 泛型之泛型約束Go泛型
- Go 創始人 Rob Pike 反對在 Go 1.18 標準庫中引入泛型支援:建議不要改動 Go 1.18 中的標準庫Go泛型
- go泛型教程Go泛型
- Go中泛型和反射比較指南Go泛型反射
- 終於!Go 1.18 將支援泛型,來聽聽Go 核心技術團隊 Russ Cox怎麼說Go泛型
- 泛型--泛型萬用字元和泛型的上下限泛型字元
- go需要泛型的場景Go泛型
- Go 需要泛型的場景Go泛型
- 泛型最佳實踐:Go泛型設計者教你如何用泛型泛型Go
- 泛型類和泛型方法泛型
- 困擾javascript初學者的閉包JavaScript
- Java最困擾你的那些事Java
- 資料庫設計的困擾資料庫
- Go 官方出品泛型教程:如何開始使用泛型Go泛型
- Go泛型基礎使用Go泛型
- Go 1.17 泛型嚐鮮Go泛型
- Java & Go 泛型對比JavaGo泛型
- Java泛型知識點:泛型類、泛型介面和泛型方法Java泛型
- 那些年困擾Linux的蠕蟲、病毒和木馬Linux
- TypeScript 泛型介面和泛型類TypeScript泛型
- 【Go】slice的一些使用技巧Go
- 簡單易懂的 Go 泛型使用和實現原理介紹Go泛型
- 轉---讓指標不再困擾你指標
- Go Internals: Go 反射 vs Java 泛型 vs cpp 模板Go反射Java泛型
- Go 快速指南:go1.18 特性Go
- TypeScript 基本型別和泛型的使用TypeScript型別泛型
- Android一種常見的佈局困擾Android
- 注意:Go 1.18版本iota的bugGo
- Scala 泛型型別和方法泛型型別