Go 泛型 【翻譯】
Go 泛型隨著 Go 1.17 版本來了,這篇文章是翻譯自? Generics in Go

Go 泛型來了! 這是多年來 Go 語言最令人激動和巨大的變化之一。本教程用簡單的語言解釋了這一部分內容:
什么是泛型
為什么我們需要泛型
泛型在Go中如何工作
以及我們可以在哪里使用泛型。
它既簡單又有趣,讓我們一起跳舞吧!
什么是泛型?
如你所知,Go 是一種類型化的語言,這意味著你程序中的每個變量和值都有一些特定的類型,如 int 或 string 。當我們寫函數時,我們需要在所謂的函數簽名中指定其參數的類型,就像這樣:
func PrintString(s string) {
這里,參數 s 是字符串類型的。我們可以想象寫出這個函數的類似版本,接受 int 、 float64 、任意結構類型等等。但這對于少數特定類型來說是不方便的,盡管我們有時可以使用接口來解決這個問題(例如在 map[string] interface 教程 中描述的),但這種方法有很多限制。
Go 中的通用函數
相反,我們想聲明一個通用函數--我們叫它 PrintAnything --它接收一個任意類型的參數(我們叫它 T ),并對它做一些事情。
下面是它的樣子。
func PrintAnything[T any](thing T) { fmt.Println(thing)}
很簡單,對嗎? any 表示 T 可以是任何類型。我們是說,對于任何類型的 T , PrintAnything 需要一個 T 型的參數。
我們如何調用這樣的函數?同樣簡單:
PrintAnything("Hello!")PrintAnything(42)PrintAnything(true)
無論我們調用函數時參數的類型是什么,這就是參數事物的類型。
限制條件
實現 PrintAnything 函數非常容易,因為 fmt 庫反正可以打印任何東西。假設我們想寫一個我們自己的版本,比如 strings.Join ,它接收一個 T 的切片,并返回一個單一的字符串,將它們全部連接在一起。讓我們試試吧。
// I have a bad feeling about this.func Join[T any](things []T) (result string) { for _, v := range things { result += v.String() } return result}
我們已經創建了一個通用函數 Join() ,對于一個任意類型的 T ,它接受一個參數,這個參數是 T 的一個切片。
output := Join([]string{"a", "b", "c"})// v.String undefined (type bound for T has no method String)
問題是,在 Join() 函數中,我們想對每個切片元素 v 調用 .String() ,把它變成一個字符串。但是 Go 需要提前檢查類型 T 是否有 String() 方法,由于它不知道 T 是什么,所以它無法做到這一點。
我們需要做的是稍微限制一下 T 的類型。我們不接受任何類型的 T ,而只對有 String() 方法的類型感興趣。任何這樣的類型都是我們的 Join() 函數可以接受的輸入,那么我們如何在 Go 中表達這種約束呢?我們使用一個接口。
type Stringer interface { String() string}
這指定了一個給定的類型有一個 String() 方法。所以現在我們可以將這個約束應用于我們的泛型函數的類型。
func Join[T Stringer] ...
由于 Stringer 保證任何 T 類型的值都有一個 String() 方法,Go 現在會很樂意讓我們在函數中調用它。但是如果你試圖用一個不符合 Stringer 的類型的切片(例如 int )來調用 Join() 函數,Go會報錯。
result := Join([]int{1, 2, 3})// int does not satisfy Stringer (missing method String)
comparable ?限制
基于方法的約束(如 Stringer )很有用,但是如果我們想對我們的通用輸入做一些不涉及調用方法的事情怎么辦?
例如,假設我們要編寫一個 Equal 函數,它接受兩個 T 類型的參數,如果它們相等則返回 true,否則返回 false。讓我們試一試:
// This won't work.func Equal[T any](a, b T) bool { return a == b}fmt.Println(Equal(1, 1))// cannot compare a == b (operator == not defined for T)
這和我們在 Join() 中的 String() 方法的問題是一樣的,但是由于我們現在沒有調用方法,所以我們不能使用基于方法集的約束。相反,我們需要將 T 限制在只能使用 == 或 != 運算符的類型上,這些類型被稱為可比較類型。幸運的是,有一個直接的方法來指定這一點:使用內置的可比較約束,而不是任何。
func Equal[T comparable] ...
constraints ? 包
為了方便起見,假設我們想對 T 的值做一些事情,而不是對它們進行比較或調用方法。例如,假設我們想為一個通用類型 T 寫一個 Max() 函數,該函數接收 T 的一個片斷并返回具有最高值的元素。我們可以試試這樣的方法。
// Nope.func Max[T any](input []T) (max T) { for _, v := range input { if v > max { max = v } } return max}
我對此不太樂觀,但讓我們看看會發生什么:
fmt.Println(Max([]int{1, 2, 3}))// cannot compare v > max (operator > not defined for T)
同樣,Go 無法提前證明類型 T 可以與 > 運算符一起使用(也就是說, T 是有序的)。我們如何解決這個問題?我們可以簡單地列出約束中所有可能的允許類型,如下所示(稱為類型列表):
type Ordered interface { type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, string}
對你的鍵盤來說幸運的是,這個和其他有用的約束已經在標準庫的 constraints 包中為我們定義了,所以我們可以導入它并像這樣使用它:
func Max[T constraints.Ordered] ...
問題解決了!
通用類型
到目前為止,太酷了。我們知道如何編寫可以接受任何類型參數的函數。但是如果我們想創建一個可以包含任何類型的類型呢?例如,“任何東西的切片”類型。事實證明這很容易:
type Bunch[T any] []T
我們是說對于任何給定的類型 T , Bunch[T] 是 T 類型值的切片。例如, Bunch[int] 是 int 的切片。我們可以以正常方式創建該類型的值:
x := Bunch[int]{1, 2, 3}
正如您所期望的,我們可以編寫采用泛型類型的泛型函數:
func PrintBunch[T any](b Bunch[T]) {
方法也是:
type StringableBunch[T Stringer] []T
Golang泛型 playground
為了讓您可以使用當前的泛型實現(例如嘗試本教程中的代碼示例),Go 團隊在此處提供了啟用泛型的 Go Playground 版本:
Golang generics playground
它的工作方式與我們熟知并喜愛的 普通 Go playground 完全相同,只是它支持本文所述的類型參數語法。因為不可能在 playground 上運行所有的 Go 代碼(例如,進行網絡調用或訪問文件系統的代碼),所以你也可以自己從 源代碼 中構建 Go 來嘗試。
問題
Go 泛型提出來的原因是什么?
可以在此處閱讀完整的設計草稿:
Type Parameters - Draft Design
Golang 會有泛型嗎?
是的。如本教程中所述,目前在 Go 中支持泛型的提議已于 2020 年 6 月在一篇博客文章中宣布: The Next Step for Generics ,現在已以我在此處描述的形式接受了用于添加 泛型的 Github 問題。
Go 博客 表示,Go 1.18 的測試版可能會包含對泛型的支持,該測試版將于 2021 年 12 月推出。
在此之前,您可以使用 Generics Playground 對其進行試驗并嘗試此處的示例。
Issue#43651 是關于泛型的實施工作的主要跟蹤問題。
標準庫會采用泛型嗎?
是的。至少會有一個 新的 slices 包來利用新功能,本教程中描述的 constraints 包,以及其他可能的包。
您可以加入 GitHub 討論,了解應該如何更新標準庫以利用新功能。
泛型 vs 接口:有沒有泛型的替代品?
正如我在 map[string] 接口 教程 中提到的,我們已經可以通過接口來編寫處理任何類型的值的 Go 代碼,而無需使用通用函數或類型。然而,如果你想寫一個實現任意類型的集合等的庫,使用泛型比使用接口要簡單方便得多。
與 any ?有什么關系?
當定義泛型函數或類型時,輸入類型必須有一個約束條件。類型約束可以是接口(比如 Stringer ),類型列表(比如 constraints.Ordered ),或者是關鍵字 comparable 。但是,如果你真的不想要任何約束,也就是說,字面上的任何類型 T 都可以呢?
表達這一點的邏輯方式是使用 interface{} 。(這個接口對一個類型的方法集完全沒有說明)。但是由于這是一個非常常見的約束,預先聲明的名字 any 被提供作為 interface{} 的別名。你只能在類型約束中使用這個名字,所以 any 不是 interface{} 的一般同義詞。
我可以使用代碼生成而不是泛型嗎?
在Go的泛型出現之前,"代碼生成器 "方法是處理此類問題的另一種傳統方式。基本上,它涉及到使用 go generate 工具 為你需要在庫中處理的每一個特定類型生成 Go 代碼。
雖然這有效,但使用起來很尷尬,靈活性有限,并且需要額外的構建步驟。雖然代碼生成對某些事情仍然有用,但我們不再需要使用它來模擬 Go 中的通用函數和類型,這一點很好。
什么是“contract”?
早期的 Go 泛型設計草案使用了與今天類似的語法,但它使用新的關鍵字 contract 來實現類型約束,而不是使用現有的 interface 接口。由于各種原因,這并不受歡迎,現在已經被取代了。
翻譯后記:雖然我還沒來得及感受泛型編程帶給我的體驗,但是還是很想推薦一下耗子哥的博客。
Go 機器翻譯
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。