【翻译】生活在没有泛型的Go语言世界里

原文在此,本翻译仅供学习与参考,如有侵权请联系我,我会迅速删除。

许多不必要的忙乱的工作是Golang特性的缺乏所造成的。一旦关于Golang的话题在Reddit或HackerNews上出现,那些热衷于五花八门的特性的黑客们便会因为这种极简语言的存在而表现出前所未有的愤慨。在这篇文章中,我会解释一种用于取代Golang所不具备的特性————泛型————的简单方法。

有许多使用泛型的理由,然而在其中99%的情况下使用Golang的内建类型(比如slice和map)或是面对对象的模式就已经足够了。这篇文章讨论的是那1%的情况————你需要一个类型安全的链表、栈或是其他数据类型同时你又不想为每个具体的类型维护一个不同的结构。

为了满足这个需求,我会使用interface{}来书写一段可复用的结构。一个处理interface{}类型的接口会很大度地接受任何一种类型————但是这样的接口不会对同质性提供任何保证,也就是说它不是类型安全的。让我们来看一段这样的栈的例子:

抽象的核心代码部分

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
31
32
package main
import "fmt"
type Stack struct {
items []interface{}
lastIndex int
}

func NewStack() *Stack {
return &Stack{items: make([]interface{}, 0)}
}

// push `v` onto the stack
func (s *Stack) Push(v interface{}) {
// if we're below capacity, add the new element to `s.lastIndex`
// otherwise, append onto the end
if s.lastIndex < len(s.items) {
s.items[s.lastIndex] = v
} else {
s.items = append(s.items, v)
}
s.lastIndex++
}

// pull the top element off the stack
func (s *Stack) Pull() interface{} {
s.lastIndex--
return s.items[s.lastIndex]
}

func (s *Stack) Size() int {
return s.lastIndex
}

为用于Int类型特化的类型安全的包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type IntStack struct { 
*Stack
}

func NewIntStack() *IntStack {
return &IntStack{NewStack()}
}

func (s *IntStack) Push(i int) {
s.Stack.Push(i)
}

func (s *IntStack) Pull() int {
// pull the top element from the stack and type assert
// it back into an int
return s.Stack.Pull().(int)
}

当然,我们还需要编写许多代码,但是它们是非常简单而且类型安全的,同时它使得我们不必重复实现许多逻辑。在这不到1%的对类似的数据结构有需求的情况下,这样的代码段是非常易于管理的,你可以在Reddit上对这篇文章进行评论或者在twitter上与联系。

写在最后

值得一提的是,Golang提供了container/list和container/ring包分别用于双向链表或环形链表的使用场景。这些类都是基于空接口类型进行操作的,就像上述的Stack类型一样。

后记:在Reddit上许多对这篇文章的评论都是与文章对于Golang没有提供泛型机制的抱怨————然而这和本文是毫不相关的。更多的诸如“没有类型安全的泛型机制很难写出正确好用的代码”或“类型安全的泛型机制的实现很复杂”正在广泛地流传开来。关于这一点,我找到了一篇评论(是来自Golang团队的Ian Lance Taylor写的),我认为这篇评论在很好地澄清了一些荒谬的夸大的同时,总结了当前Golang中需要使用泛型机制的情况的状态。我不期望任何人的想法会因此发生改变,但是哪些以讹传讹的夸大很有可能因此而从讨论中消失。
————————————————————————————————————
在一个设计良好的系统中,哪些需要使用抽象数据结构的单元往往是简单的,不需编译器的确认就很容易看出它们传递的变量类型,对于那些经过严格测试的单元来说更是如此。