Go Generic

在最近发布的 go 1.18 的版本中,泛型被正式发布,我们可以来尝鲜了,当然现在学习是最简单的时候。go泛型提案 本文大部分翻译从此。

Overview

  • 函数可以有一个额外的类型参数列表,它使用方括号,但在其他方面看起来像一个普通的参数列表: func f [ t any ](p t){ … }
  • 这些类型参数可以由常规参数和在函数体中使用。
  • 类型还可以有一个类型参数列表: 类型 m [ t any ][] t (any 就是任意类型)
  • 每个类型参数都有一个类型约束,就像每个普通参数都有一个类型: func f [ t Constraint ](p t){ … }。
  • 接口类型的类型约束。
  • 新的预声明名 any 是允许任何类型的类型约束。
  • 作为类型约束使用的接口类型可以嵌入额外的元素来限制满足约束的类型参数集:
    • 任意类型 T 限制为该类型
    • 近似元素 ~T 限制所有基础类型为T的类型
    • 并集元素 T1 | T2 |… 限制为列出的任何元素
  • 泛型函数只能使用约束允许的所有类型支持的操作。

设计实现

Type parameters

1
2
3
4
5
func Print[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}

这意味着在函数 Print 中,标识符 t 是一个类型参数,这个类型当前未知,但在调用函数时将是已知的。Any 意味着 t 可以是任何类型。如上所示,在描述普通非类型参数的类型时,类型参数可用作类型。它也可以用作函数体中的类型。

Constraints 约束

任意约束

我们知道在 GOinterface{} 是代表任意值

1
2
3
4
5
6
// Print prints the elements of any slice.
// Print has a type parameter T and has a single (non-type)
// parameter s which is a slice of that type parameter.
func Print[T interface{}](s []T) {
// same as above
}

这样我们就能够表达为接受任意类型的 Print,但是很多时候我们希望只有部分类型才能传入

类型约束

比如我们约定了一个 Stringer 类型

1
2
3
type Stringer interface {
String() string
}

我们可以这些表达 Print 函数

1
2
3
4
5
6
func Stringify[T Stringer](s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return ret
}

多类型约束的话,我们可以这么来申明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Stringer interface {
String() string
}

type Plusser interface {
Plus(string) string
}

func ConcatTo[S Stringer, P Plusser](s []S, p []P) []string {
r := make([]string, len(s))
for i, v := range s {
r[i] = p[i].Plus(v.String())
}
return r
}

泛型参数

之前我们定义一个接受任意类型的数组是

1
2
// Vector is a name for a slice of any element type.
type Vector []interface{}

现在我们可以

1
2
// Vector is a name for a slice of any element type.
type Vector[T any] []T

当然可以函数约束一样,可以使用类型约束

1
2
3
4
5
// 定义够传入 int 的数组
var v Vector[int]

// 可以泛型的实现函数
func (v *Vector[T]) Push(x T) { *v = append(*v, x) }

不过要注意

Although methods of a generic type may use the type’s parameters, methods may not themselves have additional type parameters. Where it would be useful to add type arguments to a method, people will have to write a suitably parameterized top-level function.

Go 并没有实现,函数类型中的泛型

约束单元

任意约束

就是没有约束的话,也就是 any, 我们知道了

近似约束(Approximation constraint element)

我们只需要某个特定的函数实现的话,我们就可以使用这样的约束

1
2
3
4
5
6
type MyString string

// 任意基础类型是 string 的约束
type AnyString interface {
~string
}

联合约束

1
2
3
4
5
// PredeclaredSignedInteger is a constraint that matches the
// five predeclared signed integer types.
type PredeclaredSignedInteger interface {
int | int8 | int16 | int32 | int64
}

基于这个逻辑我们就可以定义 比如 可以比较的类型

1
2
3
4
5
6
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}

接口参数

除了,我们可以使用类型放置于参数中。我们还可以使用 接口作为参数类型

1
func Map[F, T any](s []F, f func(F) T) []T { ... }

非常标准的 MAP 函数,从 F 转换为 T

1
2
3
4
5
6
7
8
9
10
var s []int
f := func(i int) int64 { return int64(i) }
var r []int64
// Specify both type arguments explicitly.
r = Map[int, int64](s, f)
// Specify just the first type argument, for F,
// and let T be inferred.
r = Map[int](s, f)
// Don't specify any type arguments, and let both be inferred.
r = Map(s, f)

尚未包含的

  • 没有专门化。没有办法编写一个泛型函数的多个版本,这些版本被设计用于处理特定的类型参数。也就是比如我处理 T any 的泛型,就没办法单独处理 T one 类型的
  • 没有元编程。没有办法编写在编译时执行的代码来生成在运行时执行的代码。
  • 没有更高层次的抽象。除了调用或实例化函数之外,没有办法使用具有类型参数的函数。除了实例化泛型类型之外,没有其他方法可以使用它。
  • 没有一般的类型描述。为了在泛型函数中使用运算符,约束列出特定的类型,而不是描述类型必须具有的特征。这很容易理解,但有时可能会受到限制。
  • 函数参数没有协边 和 逆变。
  • 无操作符方法。您可以编写一个编译时类型安全的泛型容器,但是只能使用普通方法访问它,而不能使用 c [ k ]这样的语法。
  • 没有柯里化。除了使用 helper 函数或包装器类型之外,没有办法部分地实例化泛型函数或类型。所有类型参数必须在实例化时显式传递或推断。
  • 没有可变类型参数。不支持可变参数类型参数,这将允许编写单个泛型函数,该函数接受不同数量的类型参数和常规参数。
  • 没有适配器。约束无法定义可用于支持尚未实现约束的类型参数的适配器,例如,根据 Equal 方法定义 = = 操作符,反之亦然。
  • 对于非类型的值,如常量,没有参量化。对于数组来说,这种情况最为明显,在这种情况下,编写类型 Matrix [ n int ][ n ][ n ][ n ] float64可能很方便。有时,为容器类型指定有效值(如元素的默认值)也是有用的。