为什么Go中没有泛型?


126

免责声明:我现在只和Go玩了一天,所以我很有可能错过了很多。

有谁知道为什么Go中没有对泛型/模板/ whatsInAName的真正支持?因此,有一个通用的map,但这是由编译器提供的,而Go程序员不能编写自己的实现。关于如何使Go尽可能正交的所有讨论,为什么我可以使用通用类型而不创建新类型?

尤其是在函数式编程方面,有lambda甚至闭包,但是在缺少泛型的静态类型系统中,我该如何编写泛型高阶函数,如filter(predicate, list)?好的,可以通过interface{}牺牲类型安全性来完成链接列表等。

由于在SO / Google上进行的快速搜索没有发现任何见解,因此,仿制药(如果有的话)似乎会在事后添加到Go中。我相信Thompson会比Java员工做得更好,但是为什么要排除泛型呢?还是他们已经计划并且尚未实施?


我认为值得指出:使用interface {}不会牺牲类型安全性。它是一个类型,可以断言(而不强制转换)为其他类型,但是这些断言仍会调用运行时检查以维护类型安全。
cthom06 2010年

12
interface{}牺牲了静态类型的安全性。但是,在下一个提到Scheme时,这是一个有点奇怪的投诉,因为Scheme通常没有静态类型检查。
Poolie 2010年

@poolie:我关心的是在一种语言中坚持一种范例。我不是在使用静态类型安全XOR。

2
顺便说一句,它的拼写是“ Go”,而不是“ GO”,正如您在golang.org上看到的那样。而且区分大小写。:-)
poolie 2012年

Answers:


78

您可以在这里找到以下答案:http : //golang.org/doc/faq#generics

为什么Go没有泛型类型?

泛型可能会在某个时候添加。尽管我们了解一些程序员,但我们对他们并不感到紧迫。

泛型很方便,但是它们以类型系统和运行时的复杂性为代价。尽管我们一直在考虑,但我们还没有找到一种能使价值与复杂性成比例的设计。同时,Go的内置映射和切片,以及使用空接口构造容器的能力(带有显式拆箱),意味着在许多情况下,即使编写得不太顺利,也有可能编写能够实现泛型的代码。

这仍然是一个未解决的问题。


14
@amoebe,“空接口”,拼写interface{}是最基本的接口类型,每个对象都提供它。如果制作一个容纳它们的容器,它可以接受任何(非原始)对象。因此,它与ObjectsJava中的容器非常相似。
poolie 2012年

4
@YinWang在类型推断的环境中,泛型不是那么简单。更重要的是; interface {}不等同于C中的void *指针。更好的类比是C#的System.Object或Objective-C的id类型。类型信息被保留,可以“投射”(实际上是断言)其具体类型。:这里获取坚韧不拔的细节golang.org/ref/spec#Type_assertions
TBONE

2
@tbone C#的System.Object(或Java的Object本身)本质上就是我所说的“ C的void指针”(忽略那些不能用这些语言进行指针算术的部分)。这些是静态类型信息丢失的地方。强制转换没有太大帮助,因为您会遇到运行时错误。
2013年

1
@ChristopherPfohl D的模板的编译时间开销似乎要少得多,并且通常情况下,使用模板生成的代码不会比通常情况下生成的代码多(实际上,根据情况,最终可能会生成更少的代码)。
立方2014年

3
@ChristopherPfohl我认为只有Java泛型对原始类型有装箱/拆箱问题?C#的通用化泛型没有问题。
ca9163d9 2014年

32

进行2

https://blog.golang.org/go2draft中有一个泛型设计草案。

去1

拉斯考克斯,围棋元老之一写了题为通用困境的博客文章,其中他问

…您想要慢速的程序员,慢速的编译器和肿的二进制文件,还是慢速的执行时间?

慢的程序员是没有泛型的结果,慢的编译器是由C ++引起的,就像泛型一样,慢的执行时间源于Java使用的装箱拆箱方法。

博客中未提及的第四个可能性是走C#路线。像在C ++中一样生成专用代码,但是在需要时在运行时生成。我真的很喜欢,但是Go与C#非常不同,因此这可能根本不适用……

我应该提到的是,在使用go时流行的类似于Java 1.4的通用编程技术 导致interface{}与boxing-unboxing完全一样的问题(因为这就是我们正在做的事情),除了会损失编译时类型的安全性。对于小类型(如int),Go会优化interface{}类型,以便强制转换为interface {}的int列表占据连续的内存区域,并且仅占用普通int两倍的空间。interface{}不过,从中进行投射时,仍然存在运行时检查的开销。参考

所有添加通用支持的项目(其中有几个都很有趣)都统一采用C ++编译时代码生成途径。


对于这个难题,我的解决方案是将Go设置为默认为“慢执行时间”,并可以选择配置文件并以“慢编译器和膨胀的二进制文件”模式重新编译性能最敏感的部分。不幸的是,实际上实现此类目标的人们倾向于采用C ++路线。
user7610 2015年

1
有人提到,存储的小类型(即int)的[]interface{}使用是RAM的2倍[]int。的确如此,即使是较小的类型(即字节)也最多使用16倍的RAM []byte
BMiner

C ++方法实际上没有两难选择。如果程序员选择编写模板代码,这样做的好处必须压倒缓慢编译的成本。否则,他可以按照旧方法进行操作。
John Z. Li

困境在于选择哪种方法。如果您通过使用C ++方法解决了难题,那么难题就解决了。
user7610


1

实际上,根据这篇文章:

许多人得出结论(错误地认为),Go团队的立场是“ Go永远不会拥有泛型”。相反,我们了解潜在的泛型,既可以使Go变得更加灵活和强大,也可以使Go变得更加复杂。如果我们要添加泛型,我们希望以一种方式来做到这一点,即获得尽可能多的灵活性和功能,并尽可能减少复杂性。


-1

参数多态(仿制药)正在考虑转到2

这种方法将引入合同的概念,该概念可用于表达对类型参数的约束:

contract Addable(a T) {
  a + a // Could be += also
}

这样的合同可以这样使用:

func Sum(type T Addable)(l []T) (total T) {
  for _, e := range l {
    total += e
  }
  return total
}

这是现阶段的建议


您的filter(predicate, list)函数可以使用如下类型的参数来实现:

func Filter(type T)(f func(T) bool, l []T) (result []T) {
  for _, e := range l {
    if f(e) {
      result = append(result, e)
    }
  }
  return result
}

在这种情况下,不需要约束T


1
如果您今天正在阅读此答案,请注意,合同已从提案草案中删除:go.googlesource.com/proposal
+
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.