3

Go泛型系列:再简化,省略接口

 2 years ago
source link: https://colobu.com/2021/10/24/go-generic-eliding-interface/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go泛型系列:再简化,省略接口

这是Go泛型系列文章。

其它Go泛型文章:

如果你一直关注Go泛型的设计和实现,一定知道Go泛型代码实现是通过类型参数(type parameter)实现的,当运行泛型代码时,类型参数(type parameter)由类型参数(type argument)替代。(很遗憾parameter和argument都北翻译成了中文参数)

类型参数(type parameter)也有类型,也就是描述这个参数类型行为的元数据,被成为约束(constraint)。最通用的约束就是内建的any类型,它代表任意的类型:

func Print[T any](s []T) {
for _, v := range s {
fmt.Println(v)

在Go泛型设计中, 约束是通过接口类型来实现的(interface)。因为接口类型和约束的功能黑类似,就是限定type argument必须实现type parameter的约束(方法集)。当然,为了实现泛型的功能,除了方法集之外,Go还对用来当做约束的接口做了扩展,定义了类型集(type set)的概念,比如下面是约束代表一个type argument可以是int、int8、int16、int32或int64的类型,是并(union)的关系,所以使用|符号。

type Signed interface {
int | int8 | int16 | int32 | int64

更进一步,Go还定义了~的符号,代表只要底层类型都是某个特定类型就可以,所以上面的例子可以写的更通用一些:

type Signed interface {
int | int8 | int16 | int32 | int64

这样type MyInt int定义的MyInt类型的实例也满足这个约束。

constraints 包

Go目前的实现新增加一个package,叫做constraints,用来定义内建的约束,比如常见的:

type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
type Integer interface {
Signed | Unsigned
type Float interface {
~float32 | ~float64
type Complex interface {
~complex64 | ~complex128
type Ordered interface {
Integer | Float | ~string

甚至 Russ Cox、Ian Lance Taylor他们 提议和讨论为 slice、map、chan增加必要的约束,因为它们太常用了,标准库中都可以用到。(#47203#4731947330#)。

type Slice[Elem any] interface {
~[]Elem
type Map[Key comparable, Val any] interface {
~map[Key]Val
type Chan[Elem any] interface {
~chan Elem

Rob Pike 最近新提交了一个issue,建议在Go 1.18中不要对标准库增加泛型的支持#48918。离Go 1.18发布就四个月了,很多实现还在摸索之中,这是大师给出的一个很中肯的建议,建议相关的哭的改动先增加到扩展库中(x/exp),成熟后再加到标准库中,得到了很多Gopher的赞同。这是另外一个话题了。

constraints定义常用的约束可以很好的帮助我们开发,但是你有没有感觉有点异常?

是的,依照Go泛型规范,我们必须定义一个约束,然后才能在泛型类型和泛型方法中使用,和其它语言的泛型定义相比,你有没有觉得这一点有脱裤子放屁多此一举的味道?

你看sh上面的Slice、Map、Chan的定义,是不是很冗余?为什么我们不能直接在泛型类型和方法的定义中直接使用~[]Elem~map[Key]Val~chan Elem呢?

因此fzipp提议,对于一个非接口的类型,默认等价为一个约束#48424,下面的公式很好的描述了这个功能:

[T nonInterfaceType] ≡ [T interface{~nonInterfaceType}]

在泛型的定义中,非接口类型nonInterfaceType等价于约束interface{~nonInterfaceType}, 比如~int等价于interface{~int}。这样我们就可以省略constraints包了。 这个提议北接收了,而且相关功能也加入到了go master分支中。

mattn的Go泛型例子中,将一个整形数组转换成一个chan的例子(我稍微改动成更地道的Go的写法):

package main
import (
"constraints"
"context"
"fmt"
func makeChan[T constraints.Chan[E], E any](ctx context.Context, arr []E) T {
ch := make(T)
go func() {
defer close(ch)
for _, v := range arr {
select {
case <-ctx.Done():
return
case ch <- v:
return ch
func main() {
for v := range makeChan(context.Background(), []int{1, 2, 3}) {
fmt.Println(v)

这里使用的是constraints.Chan[E]代表一个泛型的channel,现在可以用更简便的方法了:

package main
import (
"context"
func makeChan[T chan E, E any](ctx context.Context, arr []E) T {
ch := make(T)
go func() {
defer close(ch)
for _, v := range arr {
select {
case <-ctx.Done():
return
case ch <- v:
return ch

直接使用chan E就可以了,方不方便?

chan E 隐式地代表interface {chan E},使用起来更简捷,不需要额外的接口(约束)定义。

虽然Go 1.18的临近,感觉Go泛型的开发工作越来越重,甚至有一些还不明确的地方,祝福一下吧,希望它顺顺利利的推出。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK