Go1.18新特性--泛型


1. 介紹

泛型可能是1.18版本最大的更新了,畢竟官方文檔都寫在了第一條

泛型的基本介紹就不寫了,c#中有最優雅的泛型實現,可以去簡單看看

全面的泛型概述可見泛型提案  https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md

更多細節可見官方文檔  https://go.dev/ref/spec

下面只搬運一下對泛型的簡單介紹

  • 函數和類型聲明的語法接受類型參數
  • 可以通過方括號中的類型參數列表來實例化參數化函數和類型
  • 接口類型的語法現在允許嵌入任意類型以及Union和〜T類型元素。這些接口只能用作類型約束。接口現在可以定義一組類型和一組方法
  • 新的預聲明標識符any是空接口的別名,可以使用any代替interface{}
  • 新的預聲明標識符comparable表示可以使用==或!=做比較的所有類型的一個接口,它可以被用作類型約束

有三個實驗性的package在使用泛型

golang.org/x/exp/constraints

golang.org/x/exp/slices

golang.org/x/exp/maps

 

2. any關鍵字

any其實就是interface{}的別名

type any =  interface {}

 

以下代碼雖然不是泛型,但用 Go 1.18 可以正常運行,證明 any 和 interface{} 是一樣的:

// 這里的 any 並非泛型的約束,而是類型
func test(x any) any {
 return x
}

func main() {
 fmt.Println(test( "a" ))
}

 

泛型中,any 換為 interface{} 也可以:

// 注意其中的 T interface{},正常應該使用 T any
func Print[T  interface {}](s ...T) {
 for _, v :=  range s {
  fmt.Print(v)
 }
}

func main() {
 Print( "Hello, " ,  "playground\n" )
}

 

可見,之所以引入 any 關鍵字,主要是讓泛型修飾時短一點,少一些括號。any 比 interface{} 會更清爽

 

3. 泛型包slices

目前,slices 包有 14 個函數,可以分成幾組:

  • slice比較
  • 元素查找
  • 修改slice
  • 克隆slice

其中,修改slice分為插入元素、刪除元素、連續元素去重、slice擴容和縮容

 

3.1 slice比較

比較兩個 slice 中的元素,細分為是否相等和普通比較:

func Equal[E comparable](s1, s2 []E) bool
func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq  func (E1, E2) bool) bool
func Compare[E constraints.Ordered](s1, s2 []E) int
func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp  func (E1, E2) int) int

 

其中 comparable 約束是語言實現的(因為很常用),表示可比較約束(相等與否的比較)。主要,其中的 E、E1、E2 等,只是泛型類型表示,你定義時,可以用你喜歡的,比如 T、T1、T2 等。

看一個具體的實現:

func Equal[E comparable](s1, s2 []E) bool {
 if len(s1) != len(s2) {
  return false
 }
 for i, v1 :=  range s1 {
  v2 := s2[i]
  if v1 != v2 {
   return false
  }
 }
 return true
}

 

3.2 元素查找

在 slice 中查找某個元素,分為普通的所有查找和包含判斷:

func Index[E comparable](s []E, v E) int
func IndexFunc[E any](s []E, f  func (E) bool) int
func Contains[E comparable](s []E, v E) bool

 

其中,IndexFunc 的類型參數沒有使用任何約束(即用的 any),說明查找是通過 f 參數進行的,它的實現如下:

func IndexFunc[E any](s []E, f  func (E) bool) int {
 for i, v :=  range s {
  if f(v) {
   return i
  }
 }
 return -1
}

 

參數 f 是一個函數,它接收一個參數,類型是 E,是一個泛型,和 IndexFunc 的第一個參數類型 []E 的元素類型保持一致即可,因此可以直接將遍歷 s 的元素傳遞給 f

 

3.3 修改slice

一般不建議做相關操作,因為性能較差。如果有較多這樣的需求,可能需要考慮更換數據結構

// 往 slice 的位置 i 處插入元素(可以多個)
func Insert[S ~[]E, E any](s S, i int, v ...E) S
// 刪除 slice 中 i 到 j 的元素,即刪除 s[i:j] 元素
func Delete[S ~[]E, E any](s S, i, j int) S
// 將連續相等的元素替換為一個,類似於 Unix 的 uniq 命令。Compact 修改切片的內容,它不會創建新切片
func Compact[S ~[]E, E comparable](s S) 
func CompactFunc[S ~[]E, E any](s S, eq  func (E, E) bool) S
// 增加 slice 的容量,至少增加 n 個
func Grow[S ~[]E, E any](s S, n int) S
// 移除沒有使用的容量,相當於縮容
func Clip[S ~[]E, E any](s S) S

 

以上類型約束都包含了兩個:

  • S ~[]E:表明這是一個泛型版 slice,這是對 slice 的約束。注意 [] 前面的 ~,表明支持自定義 slice 類型,如 type myslice []int
  • E any 或 E comparable:對上面 slice 元素類型的約束。

 

3.4 克隆slice

獲得 slice 的副本,會進行元素拷貝,注意,slice 中元素的拷貝是淺拷貝,非值類型不會深拷貝。

func Clone[S ~[]E, E any](s S) S {
 // Preserve nil in case it matters.
 if s == nil {
  return nil
 }
 return append(S([]E{}), s...)
}

 

3.5 總結

因為泛型的存在,相同的功能對於不同類型的slice可以少寫一份代碼,如果想使用slice泛型的相關操作,建議復制golang.org/x/exp中的函數進行使用或修改

 

4. 泛型包maps

目前maps包只有8個函數,實現的功能也比較基礎,大概包含了以下幾種操作類型:

  • 清空map
  • 拷貝、克隆
  • 相等判斷
  • kv操作

 

4.1 清空map

清空map就一個函數,實現起來也非常簡單

func Clear[M ~ map [K]V, K comparable, V any](m M)

 

4.2 拷貝克隆

其中包含了拷貝和克隆,作用稍有不同

func Clone[M ~ map [K]V, K comparable, V any](m M) M
func Copy[M ~ map [K]V, K comparable, V any](dst, src M)

 

很容易理解克隆和拷貝的區別,克隆就是返回M的一份淺拷貝,兩份副本指向同一個map,已經預感到類似於slice的坑了...,拷貝就是將src map中所有的kv復制到dst map中,如果dst已經存在key,將會被覆蓋。

 

4.3 相等判斷

主要是判斷兩個兩個map是否相等,從函數簽名可以看出兩個函數的區別

func Equal[M1, M2 ~ map [K]V, K, V comparable](m1 M1, m2 M2) bool
func EqualFunc[M1 ~ map [K]V1, M2 ~ map [K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq  func (V1, V2) bool) bool

 

Equal函數限定兩個map的key和value必須是可比較的,也就是說kv必須都可以使用==和!=做判斷,而EqualFunc限定兩個map的key必須是可比較的,而value的比較按照eq函數定義的比較規則

 

4.4 kv操作

kv操作包括了刪除指定kv、返回所有的key、返回所有的value

func DeleteFunc[M ~ map [K]V, K comparable, V any](m M, del  func (K, V) bool)
func Keys[M ~ map [K]V, K comparable, V any](m M) []K
func Values[M ~ map [K]V, K comparable, V any](m M) []V

 

值得一提的是,keys和values函數返回的元素都是無序的,這三個方法讓我想到了kv數據庫...

 

4.5 總結

maps提供的函數比slices更簡單一些,關於kv操作個人覺得會有一些應用場景,map的相等判斷在泛型里可能不是很有必要了...

 

5. 泛型的簡單使用案例

來看一個CRUD接口的定義

type Model  interface {
    ID() string
}

type DataProvider[MODEL Model]  interface {
    FindByID(id string) (MODEL, error)
    List() ([]MODEL, error)
    Update(id string, model MODEL) error
    Insert(model MODEL) error
    Delete(id string) error
}

 

現在我們可以定義一個使用DataProvider的HTTP處理程序:

type HTTPHandler[MODEL Model]  struct {
    dataProvider DataProvider[MODEL]
}

func (h HTTPHandler[MODEL]) FindByID(rw http.ResponseWriter, req *http.Request) {
    // validate request here
    id =  // extract id here
    model, err := h.dataProvider.FindByID(id)
    if err != nil {
        // error handling here
        return
    }
    err = json.NewEncoder(rw).Encode(model)
    if err != nil {
        // error handling here
        return
    }
}

 

我們可以為每個方法實現一次,然后就完成了。我們甚至可以在事務的另一端創建一個客戶端,只需要為基本方法實現一次。

為什么在此使用泛型而不是簡單的我們已經定義的Model接口》

與在此使用Model類型本身相比,泛型有一些優點:

  • 使用泛型方法,DataProvider根本不需要知道Model,也不需要實現它。它可以簡單地提供非常強大的具體類型
  • 我們可以擴展這個解決方法並使用具體類型進行操作。讓我們看看插入或者更新的驗證器是什么樣子
type HTTPHandler[MODEL any]  struct {
    dataProvider DataProvider[MODEL]
    InsertValidator  func (new MODEL) error
    UpdateValidator  func (old MODEL, new MODEL) error
}

 

在這個驗證器中是泛型方法的真正優勢所在。我們將解析 HTTP 請求,如果定義了自定義的 InsertValidator,那么我們可以使用它來驗證模型是否檢出,我們可以以類型安全的方式進行並使用具體模型:

type User  struct {
    FirstName string
    LastName string
}

func InsertValidator(u User) error {
    if u.FirstName ==  "" { ... } 
    if u.LastName ==  "" { ... }
}

 

所以我們有一個泛型的處理器,我們可以用自定義回調來調整它,它直接為我們獲取有效負載。沒有類型轉換。沒有 map。只有結構體本身。

 

參考:

https://go.dev/doc/go1.18

https://pkg.go.dev/golang.org/x/exp

https://mp.weixin.qq.com/s/1Tm_E86cgTrhzZ2Rnm7UjA

https://mp.weixin.qq.com/s/tjHOd6jvGj7tpmf1K4wlYg

https://mp.weixin.qq.com/s/wg5fNsB--5nIgJ6EBBc0PA

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM