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

 

posted @ 2022-03-17 09:42  aganippe  阅读(2308)  评论(0编辑  收藏  举报