Go泛型

Go 语言本身不支持泛型 generics。我们这里主要看下怎么处理泛型的需求。

泛型数据

如果要存储泛型数据,Go 提供了空接口 interface{}。可以将任意的数据结构存储到 interface{} 中,然后使用的时候需要做一下 cast 转换。

泛型函数

这种使用比较常用,意思是针对不同的数据类型,需要运行同样的一套函数化的流程或者算法。

目前 Go 并不支持这种传统意义上的泛型编程。Jon Bodner 在他的 Closures are the Generics for Go 这篇文章中提供了一种间接实现泛型函数需求的做法,就是利用回调形式的闭包。

简单来说,就是将需要泛型抽象的一套流程和算法封装成一个公共接口,并且提供一个回调,可以在回调时候利用闭包的特性来读取或者修改变量 T,而 T 在通常的泛型编程中是需要传给泛型函数的。

下面是一个C++ 中的泛型函数的使用,compare 就是一套函数化的流程或者算法。

1
2
3
4
5
6
7
template <typename T>
int compare(const T &lhs, const T &rhs)
{
if (lhs < rhs) return -1;
if (rhs < lhs) return 1;
return 0;
}

在 Go 中如果实现类似的效果呢?方法就是回调闭包函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func compare(fnCmp func() int) bool {
ret := fnCmp()
return ret >= 0
}

func main() {
var a int = 1;
var b int = 2;
result := compare(func() int {
if (a < b) {return -1}
if (a > b) {return 1}
return 0
})
fmt.Println(result)
}

通过闭包的特性,可以不需要传之前需要的泛型参数 T,就可以对变量进行读取和修改。

我们只需要将相同的流程抽象成一个公共接口,这里是compare,然后将不同的部分通过回调闭包的形式作为参数让公共接口调用即可,这里是 fnCmp func() int 参数。

另一个比较恰当的例子是数据库的批量分页拉取。针对每个业务的数据库表,都可能会需要分页拉取 table 里的所有数据,就有必要将分页拉取的操作封装成一个公共接口,拉取后通过闭包参数来让调用方自定义后续操作。

1
2
3
4
5
6
7
var records []*DBTerritoryRespawn
batchGet(dbtbl, fields, wheres, func(rows [][]hqdb.TbField){
for _, row := range rows {
record := do_something_with(row)
records = append(records, record)
}
})

每次拉取一页(batchGet 内部调用一次闭包函数),都可以修改最终的结果 records

利用闭包,我们可以方便的抽象出一个通用的排序接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import(
"sort"
)

type sorter struct {
len int
fnSwap func(i, j int)
fnLess func(i, j int) bool
}
func (s sorter) Len() int { return s.len }
func (s sorter) Swap(i, j int) {
s.fnSwap(i, j)
}
func (s sorter) Less(i, j int) bool {
return s.fnLess(i, j)
}

//slice排序
func Sort(len int, fnSwap func(i, j int), fnLess func(i, j int) bool) {
sort.Sort(sorter{len, fnSwap, fnLess})
}

func main() {
data := []int{2, 1, 5, 3, 4}
Sort(len(data), func(i, j int) {
data[i], data[j] = data[j], data[i]
}, func(i, j int) bool {
return data[i] < data[j]
})
}

只不过这里用了2个闭包函数 fnSwap 和 fnLess。

以上通过 closure 闭包提供的解决方式实质上使用了软件理论中的side-effects来实现的,意思是函数调用会影响调用方的数据。现代编程理论认为这是一种不好的方式,和其相对的是函数化编程。

这里仅仅是提供一个泛型的一种 work around,所以还是期待未来 Go 能支持真正意义上的泛型。

参考资料

Closures are the Generics for Go