funcDouble1[TInteger](s []T) []T { ret := make([]T, 0, len(s)) for _, v := range s { ret = append(ret, v+v) } return ret }
funcDouble2[TInteger](s []any) []T { ret := make([]T, 0, len(s)) for _, v := range s { sum := v.(int) + v.(int) // ret = append(ret, sum) // INVALID, T can not be inferred switch retTyped := (any)(&ret).(type) { case *[]int: *retTyped = append(*retTyped, sum) } } return ret }
var V1 = Double(MySlice{1}) fmt.Printf("%T\n", V1) //[]int fmt.Println(V1) //[2]
var V2 = DoubleDefined(MySlice{1}) fmt.Printf("%T\n", V2) //main.MySlice fmt.Println(V2) //[2] }
type Integer interface{ ~int }
funcDouble[EInteger](s []E) []E { r := make([]E, len(s)) for i, v := range s { r[i] = v + v } return r } funcDoubleDefined[S ~[]E, EInteger](s S)S { r := make(S, len(s)) for i, v := range s { r[i] = v + v //可以编译和运行,但可能在GoLand中被标注红色波浪线报错 } return r }
首先通过入参,可以得到 S 就是 MySlice 类型。
{S -> MySlice}
其次,通过 S 是 ~[]E 知道 MySlice 是底层为 E 切片的类型,而 MySlice 是 int 切片,所以 E 是 int。
{S -> MySlice, E -> int}
这样一来,就确定了此函数的两个类型参数。
指针方法
下面例子尝试用FromStrings方法将字符串切片,转化为符合Setter约束的类型切片。
package main
import"strconv"
type Setter interface { Set(string) }
funcFromStrings[TSetter](s []string) []T { //T在这里是 *Settable,其零值是nil result := make([]T, len(s)) for i, v := range s { result[i].Set(v) } return result }
type Settable int
func(p *Settable)Set(s string) { i, _ := strconv.Atoi(s) // assign to nil *p = Settable(i) }
funcmain() { //nums := FromStrings[Settable]([]string{"1", "2"}) //× nums := FromStrings[*Settable]([]string{"1", "2"}) //✓ × for _, s := range nums { println(s) } }
funcFromStrings[Tany, PTSetter[T]](s []string) []T { //T在这里是 Settable,其零值是0 result := make([]T, len(s)) for i, v := range s { p := PT(&result[i]) //强转 p.Set(v) } return result }
type Settable int
func(p *Settable)Set(s string) { i, _ := strconv.Atoi(s) // real code should not ignore the error *p = Settable(i) }
funcmain() { nums := FromStrings[Settable]([]string{"1", "2"}) for _, s := range nums { println(s) } }
这样的方式显得比较尴尬,但是可以实现需求,而且使用类型推断可以显得不那么尴尬。
{T -> Settable}
{T -> Settable, PT -> *T}
{T -> Settable, PT -> *Settable}
自引用约束
考虑以下代码如何简化
type equalInt int
func(a equalInt)Equal(b equalInt)bool { return a == b }
funcindexEqualInt(s []equalInt, e equalInt)int { return Index[equalInt](s, e) }
type Equaler[T any] interface { Equal(T) bool }
funcIndex[TEqualer[T]](s []T, e T)int { for i, v := range s { if e.Equal(v) { return i } } return-1 }
type Stringish interface { string | fmt.Stringer }
上面的实例在编译阶段就会报错。
cannot use fmt.Stringer in union (fmt.Stringercontainsmethods)
有些约束不可能有满足的类型
下面代码描述了数据类型要是int和float32的,且要有String() string方法实现。
type Unsatisfiable interface { int | float32 String() string }
然而现实中,无论是int还是float32,他们都没有String() string方法,所以是无法找到满足约束的类型,即上面的约束有空类型集合(empty type set)。
例子
Map/Reduce/Filter
funcMap[T1, T2any](s []T1, f func(T1)T2) []T2 { r := make([]T2, len(s)) for i, v := range s { r[i] = f(v) } return r }
funcReduce[T1, T2any](s []T1, initializer T2, f func(T2, T1)T2) T2 { r := initializer for _, v := range s { r = f(r, v) } return r }
funcFilter[Tany](s []T, f func(T)bool) []T { var r []T for _, v := range s { if f(v) { r = append(r, v) } } return r }
funcmain() { s := []int{1, 2, 3} floats := Map(s, func(i int)float64 { returnfloat64(i) }) // Now floats is []float64{1.0, 2.0, 3.0}. sum := Reduce(s, 0, func(i, j int)int { return i + j }) // Now sum is 6. evens := Filter(s, func(i int)bool { return i%2 == 0 }) // Now evens is []int{2}. }
map keys
funcKeys[Kcomparable, Vany](m map[K]V) []K { r := make([]K, 0, len(m)) for k := range m { r = append(r, k) } return r }
funcmain() { k := Keys(map[int]int{1: 2, 2: 4}) // Now k is either []int{1, 2} or []int{2, 1}. }
等等。。。
数据库翻页读取泛型案例
(假代码模仿通过rpc接口翻页读取)
package main
import ( "fmt" "math/rand" "time" )
funcinit() { rand.Seed(time.Now().UnixNano()) }
type Order struct { Id *int OrderName *string } type SKU struct { Id *int SKUName *string } type BaseResponse struct { StatusCode int32 } type GetOrdersResponse struct { Data []*Order BaseResponse }