https://bitfieldconsulting.com/posts/type-sets
<aside> 💡
A set is a Many that allows itself to be thought of as a One.
—Georg Cantor, quoted in Rudy Rucker’s “Infinity and the Mind”
</aside>
泛型编程的要点是能够编写操作多于一种具体数据类型的代码。这样一来,我们就不必重复编写相同的代码,一遍又一遍,为每种需要处理的数据类型各写一份代码。
但是对数据类型过于自由和宽松也会走得太远:接受字面上任何类型数据的类型参数并不太有用。我们需要约束(constraints)来缩小一个函数能处理的类型集合。当类型集合是无限的(例如 [T any]),我们几乎无法对这些值做任何事情,因为我们对它们一无所知。
那么,我们如何编写更灵活的约束,使得它们所包含的类型集合既足够广泛以实用,又足够狭窄以便可用呢?
我们已经知道,一种接口可以通过列出方法元素(method elements)来指定允许的类型范围,比如 String()
这个字符串方法。我们将使用“基本接口”(basic interface)一词来描述只包含方法元素的接口,但现在让我们引入另一种接口。它不是列出类型必须拥有的方法,而是直接指定允许的类型集合。
例如,假设我们想写一个泛型函数 Double,将一个数字乘以二,并且我们想要一个类型约束,只允许 int 类型的值。我们知道 int 没有任何方法,所以不能用任何基本接口作为约束。那么我们该如何写呢?
好吧,方法如下:
type OnlyInt interface {
int
}
非常直接!它看起来就像一个普通的接口定义,只不过不是包含方法元素,而是包含一个类型元素(type element),由一个命名类型组成。在这个例子中,这个命名类型是 int。
我们如何使用这样的约束呢?那我们来写一个 Double 函数:
func Double[T OnlyInt](v T) T {
return v * 2
}
换句话说,对于满足 OnlyInt 约束的某个类型 T,Double 接受一个 T 类型的参数并返回一个 T 类型的结果。
注意,我们现在有了一个解决方案,针对之前尝试编写 AddAnything 函数时遇到的问题:如何在参数化函数中启用 *
运算符(或其他算术运算符)。由于 T 只能是 int(得益于 OnlyInt 约束),Go 可以保证 *
运算符能用于 T 类型的值。
不过,这还不是完整的答案,因为还有其他支持 *
运算符的类型,它们不会被此约束允许。而且,如果我们只打算支持 int,也完全可以写一个接受 int 参数的普通函数。
因此,我们需要能够稍微扩大约束允许的类型范围,但不能超出支持 *
运算符的类型。我们该如何做到这点呢?
哪些类型能满足约束 OnlyInt?答案是,只有 int!为了扩大这个范围,我们可以创建一个指定多个命名类型的约束: