Go泛型前瞻(2021.9.17)

时间:2024-10-10 19:28:54

目前,泛型方案尚未正式发布,此文章中的内容均基于Taylor在8月份的最新proposal以及当前的go master分支。同时笔者也会尽可能持续更新。

目录

Prologue

在Go 1.18中使用泛型

编译第一个泛型程序

使用Goland

使用gopls

初探基本概念

类型参数(Type parameters)

泛型类型的方法

自引用的泛型类型(Generic type referring to itself)

类型参数的互相引用

约束(Constraints)

类型集合(Type sets)

约束的基本概念

定义一个约束

两个预定义的约束

空类型集合

结构化约束(Structual constraints)

情况一

情况二

约束可执行的方法与操作

依赖于约束自身的约束

类型推断(Type inference)

类型归一判断(Type Unification)

函数参数类型推断

约束的类型判断

再探基本概念

实例化(Instantiation)

类型可转换性(Convertiblity)

零值问题

Epilogue


Prologue

golang缺失泛型无疑是gophers的心头之痛,很多时候哪怕是Max或者IfElse这样简单的函数,我们都只能在臃肿的代码和类型转换/断言之间二选一。支持泛型的呼声也从09年一直到了今天。从2010年开始,已经设计了数版泛型方案:

// ============2010-06============

type Vector(t) []t
type Lesser(t) interface {
        Less(t) bool
}
func Min(a, b t type Lesser(t)) t {
        if (b) {
                return a
        }
        return b
}

// ============2011-03============

gen [T] type Vector []T
gen [T] type Comparer interface {
    Compare(T) int
}
gen [T Comparer[T]] type SortableVector []T
gen [T1, T2] (
    type Pair struct { 
        first  T1,
        second T2, 
    }
    func MakePair(first T1, second T2) Pair {
        return &Pair{first, second}
    }
)

// ============2013-12============

type [T] Equaler interface {
    Equal(T) bool
}
type [T] Lessable T
func [T] (a Lessable[T]) Less(b T) bool {
    return a < b
}

// ============2016-09============

const func AsWriterTo(reader gotype) gotype {
    switch reader.(type) {
    case :
        return reader
    default:
        type WriterTo struct {
            reader
        }
        func (t *WriterTo) WriteTo(w ) (n int64, err error) {
            return (w, )
        }
        return WriterTo (type)
    }
}

上面的一些设计还是有不完善之处的,直到19年,Taylor提出了合约(Contract)的概念,参见:/proposal/+/master/design/,这个方案已经相对成熟了

// define contract
contract stringer(T) {
    T String() string
}
constract C(T1, T2, T3) {
    T1 int, float64
    T2 Method(T1) T1
}

// use contracts
type S(type T C) struct {...}
type I(type T C) interface {...}
func F(type T C)(params ...T) T {...}

但是,最后contract的设计还是被放弃。Go团队转向了类型参数(Type Parameter)的设计。Taylor在今年3月份,表示类型参数的设计已经被接受,同时golang预计将在2022年初的Go 1.18版本中支持此功能。目前最新的go分支已支持泛型的编译。

ref: /proposal/+/refs/heads/master/design/

在Go 1.18中使用泛型

编译第一个泛型程序

  1. 在GitHub上拉取最新的代码,repo: /golang/
  2. 进入src/目录
  3. 运行`bash ./` 
  4. 编译完成后,进入到 ../bin目录,就可以得到最新的go文件
➜  base-eg ../../go/bin/go version
go version devel go1.18-50c69cc3a9 Wed Sep 8 06:59:06 2021 +0000 darwin/amd64

下面写一个简单的程序,从获取一个map中,所有的value值为例。或许你看不太懂含义,我们稍后解释。

package main

import (
    "fmt"
)

func main() {
    example := map[int]int {
        1: 2,
        2: 4,
        3: 6,
    }
    (MapValues(example))
    example2 := map[string]string {
        "1" : "one",
        "2" : "two",
        "3" : "three",
    } 
    (MapValues(example2))
}

func MapValues[T comparable, K any](m map[T]K) []K {
    result := make([]K, 0, len(m))
    for _, value := range m {
        result = append(result, value)
    }
    return result
}

执行命令go build -gcflags=-lang=go1.18得到编译产物,运行后,结果为:

➜  base-eg ../../go/bin/go build -gcflags=-lang=go1.18
➜  base-eg ./base-eg
[2 4 6]
[one two three]

编译命令你或许见过其他版本的,我来简单解释一下

  1. 过去需要手动指定-G=3,编译命令为:go build -gcflags=-G=3 
  2. 在8月23号的一个commit中,-G=3默认添加:343732: cmd/compile: enable -G=3 by default ,此时你可以通过 go build 直接构建
  3. 但是这个引入了一些问题,所以26号又添加了一个commit:344871: cmd/compile: always accept 1.18 syntax but complain if not 1.18在检测到go版本低于1.18时禁用泛型编译,所以需要手动指定版本为1.18

使用Goland

实际上,GoLand也已初步支持了泛型,不过这个我没鼓捣成功,博客用的是Goland 20.3,可能是因为我的Goland 20.2不够吧。

可以参考:

Experimenting with Go Type Parameters (Generics) in GoLand | The GoLand BlogIn today's article, we will experiment with generics in Go, and their latest form, Type we start, let's take a quick look at the proposal.../go/2020/11/24/experimenting-with-go-type-parameters-generics-in-goland/

使用gopls

适用于vscode等支持LSP的,以vscode为例

  1. 在中,将goroot指定为最新的go master分支的目录
  2. 重启vs code
  3. 然后就会提示你,goroot变更,一些工具需要重新编译,然后点击确定,等待之后,就可以支持泛型语法了
  4. 如果不行,go get 一下gopls的master

初探基本概念

类型参数(Type parameters)

如果说函数是传入数值来复用代码,那么泛型便是传入类型来复用代码。Go团队决定允许在函数以及类型定义时,传入类型参数来支持泛型。

泛型函数示例:

func GenericPrintSlice[T any] (arr []T) {
    for _, ele := range(arr) {
        (ele)
    }
}

GenericPrintSlice[int]([]int{1, 2, 3})

泛型类型(Generic Types)示例:

type Vector[T any] []T

var intArray Vector[int]
var stringArray Vector[string]

我们可以注意到如下几点

  1. 类型参数使用方括号传入&#