什么是泛型

泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。

Golang 泛型基本用法

示例

map 操作

package main

import (
	"fmt"
)

func mapFunc[T any, M any](a []T, f func(T) M) []M {
	n := make([]M, len(a), cap(a))
	for i, e := range a {
		n[i] = f(e)
	}
	return n
}

func main() {
	vi := []int{1, 2, 3, 4, 5, 6}
	vs := mapFunc(vi, func(v int) string {
		return "<" + fmt.Sprint(v * v) + ">"
	})
	fmt.Println(vs)
}

min max 函数

package main

import (
	"fmt"
)

type ordered interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
}

func max[T ordered](a []T) T {
	m := a[0]
	for _, v := range a {
		if m < v {
			m = v
		}
	}
	return m
}

func min[T ordered](a []T) T {
	m := a[0]
	for _, v := range a {
		if m > v {
			m = v
		}
	}
	return m
}

func main() {
	vi := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	result := max(vi)
	fmt.Println(result)

	vii := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
	result2 := min(vii)
	fmt.Println(result2)
}

Set

// Package sets implements sets of any comparable type.
package sets

// Set is a set of values.
type Set[T comparable] map[T]struct{}

// Make returns a set of some element type.
func Make[T comparable]() Set[T] {
	return make(Set[T])
}

// Add adds v to the set s.
// If v is already in s this has no effect.
func (s Set[T]) Add(v T) {
	s[v] = struct{}{}
}

// Delete removes v from the set s.
// If v is not in s this has no effect.
func (s Set[T]) Delete(v T) {
	delete(s, v)
}

// Contains reports whether v is in s.
func (s Set[T]) Contains(v T) bool {
	_, ok := s[v]
	return ok
}

// Len reports the number of elements in s.
func (s Set[T]) Len() int {
	return len(s)
}

// Iterate invokes f on each element of s.
// It's OK for f to call the Delete method.
func (s Set[T]) Iterate(f func(T)) {
	for v := range s {
		f(v)
	}
}

尝鲜试用

Golang 类型当前状况与泛型的意义

当前

interface

弊端:类型转换、缺乏编译时的约束

package main

import "fmt"

func printStr(x interface{}) {
	value, ok := x.(string)
	if !ok {
		fmt.Println("It's not ok for type string")
		return
	}
	fmt.Println("The value is ", value)
}

func main() {
	printStr(123)
}
// It's not ok for type string

为不同类型单独编写

弊端:API与代码实现不整洁、工作量大等

示例:当前标准库的 sort, math 等

代码生成

弊端:需学习第三方代码生成工具、go:generate、AST等知识,不通用

示例:

https://github.com/cheekybits/genny

package gogenerate

import "github.com/cheekybits/genny/generic"

//go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "KeyType=string,int ValueType=string,int"

type KeyType generic.Type
type ValueType generic.Type

type KeyTypeValueTypeMap map[KeyType]ValueType

func NewKeyTypeValueTypeMap() map[KeyType]ValueType {
	return make(map[KeyType]ValueType)
}

Go 官方泛型意义

  • 通用操作与类型约束
  • 函数式编程
  • 简化标准库和第三方库的实现

………

Golang 泛型发展历史

简述时间作者
[Type Functions]2010 年Ian Lance Taylor
Generalized Types2011 年Ian Lance Taylor
Generalized Types v22013 年Ian Lance Taylor
Type Parameters2013 年Ian Lance Taylor
go:generate2014 年Rob Pike
First Class Types2015 年Bryan C.Mills
Contracts2018 年Ian Lance Taylor, Robert Griesemer
Contracts2019 年Ian Lance Taylor, Robert Griesemer
Redundancy in Contracts(2019)’s Design2019 年Ian Lance Taylor, Robert Griesemer
Constrained Type Parameters2020 年Ian Lance Taylor, Robert Griesemer
Featherweight Go2020年Ian Lance Taylor, Robert Griesemer
合并进 master、发布 Go 1.18、改进标准库 等2021 - 2022 年

Golang 泛型实现原理

他山之石

不同语言泛型实现

boxing (装箱) VS monomorphization (单态化)

img

泛型的困境

https://research.swtch.com/generic

  • 拖累程序员: 比如 C 语言,增加了程序员的负担,需要曲折的实现,但是不对增加语言的复杂性
  • 拖累编译器: 比如 C++,增加了编译器的负担,可能会产生很多冗余的代码,重复的代码还需要编译器斟酌删除,编译的文件可能非常大
  • 拖累执行时间: 比如 Java,将一些装箱成 Object, 进行类型擦除。虽然减少代码冗余、减少空间,但是需要装箱拆箱操作,代码效率低

Go

Keith H. Randal 的三个方案:

Generics implementation - Dictionaries

编译时实例化字典,字典包含为类型参数实例化的类型信息

Generics implementation - Stenciling

模板生成,为每个实例化类型生成一套独立的代码

Generics implementation - GC Shape Stenciling

混合实现,shape类型相同的类型,使用字典区分类型的不同行为,

类型的shape是指它对内存分配器/垃圾回收器呈现的方式,

包括大小、所需的对齐方式以及类型的哪些部分包含指针

参考资料

Go 官方和社区资料:

https://go.dev/doc/go1.18

https://go.dev/doc/tutorial/generics

https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md

https://go.dev/blog/generics-proposal

https://go.dev/blog/generics-next-step

https://go.dev/blog/go2draft

https://go.dev/blog/why-generics

https://github.com/golang/proposal/blob/master/design/generics-implementation-dictionaries.md

https://github.com/golang/proposal/blob/master/design/generics-implementation-stenciling.md

https://github.com/golang/proposal/blob/master/design/generics-implementation-gcshape.md

示例用法、文章分析等:

https://github.com/mattn/go-generics-example

https://colobu.com/2021/08/30/how-is-go-generic-implemented/

https://coolshell.cn/articles/21615.html

https://draveness.me/whys-the-design-go-generics/

https://taoshu.in/go/go-generics-design.html

https://research.swtch.com/generic

https://thume.ca/2019/07/14/a-tour-of-metaprogramming-models-for-generics/