【问题标题】:Why no generics in Go?为什么 Go 中没有泛型?
【发布时间】:2011-04-24 03:34:22
【问题描述】:

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

有人知道为什么 Go 中没有对泛型/模板/whatsInAName 的真正支持吗?所以有一个通用的map,但这是由编译器提供的,而 Go 程序员不能编写自己的实现。既然大家都在谈论让 Go 尽可能正交,为什么我可以使用泛型类型但不能创建新类型?

特别是在函数式编程方面,有 lambda,甚至是闭包,但是对于缺少泛型的静态类型系统,我该如何编写像 filter(predicate, list) 这样的泛型高阶函数?好的,链表之类的可以用interface{}牺牲类型安全来完成。

由于在 SO / Google 上的快速搜索没有发现任何见解,看起来泛型(如果有的话)将作为事后的想法添加到 Go 中。我确实相信 Thompson 比 Java 的人做得更好,但为什么要把泛型排除在外呢?还是他们已经计划好但尚未实施?

【问题讨论】:

  • 我认为值得指出的是:使用 interface{} 不会牺牲类型安全。它是一种类型,可以断言(而不是强制转换)为其他类型,但这些断言仍会调用运行时检查以维护类型安全。
  • interface{} 牺牲了 static 类型的安全性。然而,当提到 Scheme 是下一段时,这是一个有点奇怪的抱怨,因为 Scheme 通常没有静态类型检查。
  • @poolie:我关心的是在一种语言中坚持一个范式。要么我使用的是静态类型安全 XOR,要么没有。
  • 所以为了让您了解最新情况 > 一个实现泛型类型的语言提案已被接受 > 包含在该语言中。如果一切顺利,它将 > 在 Go 1.18 版本中可用。这是proposal

标签: generics functional-programming go


【解决方案1】:

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

为什么 Go 没有泛型类型?

可能会在某些时候添加泛型。尽管我们知道有些程序员有这种感觉,但我们并不觉得他们有紧迫感。

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

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

【讨论】:

  • @amoebe,“空接口”,拼写为interface{},是最基本的接口类型,每个对象都提供它。如果您制作一个容纳它们的容器,它可以接受任何(非原始)对象。所以它非常类似于 Java 中保存Objects 的容器。
  • @YinWang 泛型在类型推断环境中并不是那么简单。更重要的是; interface{} 不等同于 C 中的 void* 指针。更好的类比是 C# 的 System.Object 或 Objective-C 的 id 类型。类型信息被保留并且可以“转换”(实际上是断言)回到它的具体类型。在此处获取详细信息:golang.org/ref/spec#Type_assertions
  • @tbone C# 的 System.Object(或 Java 的 Object 本身)本质上就是我所说的“C 的 void 指针”(忽略在这些语言中不能进行指针运算的部分)。这些是静态类型信息丢失的地方。强制转换不会有太大帮助,因为您会遇到运行时错误。
  • @ChristopherPfohl D 的模板似乎有相当少的编译时间开销,而且通常您不会使用模板生成更多的代码(实际上,您最终可以less 代码视情况而定)。
  • @ChristopherPfohl 我认为只有 Java 泛型对原始类型有装箱/拆箱问题? C# 的具体化泛型没有问题。
【解决方案2】:

Generics 目前计划用于 Go 1.18 版本,该版本目前处于 Beta 阶段。来源:https://go.dev/doc/tutorial/generics

“去 2”

泛型设计是在 Go2 的保护下开始的,最初是在 https://blog.golang.org/go2draft,在接下来的 3 年里还有几个草案规范。

去 1

Russ Cox,一位围棋老手写了一封blog post entitled The Generic Dilemma,他在其中询问

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

程序员速度慢是因为没有泛型,编译器速度慢是由 C++ 之类的泛型造成的,而执行时间慢则源于 Java 使用的装箱拆箱方法。

博客中没有提到的第四种可能性是走 C# 路线。像在 C++ 中一样生成专门的代码,但在需要时在运行时生成。我真的很喜欢它,但 Go 与 C# 非常不同,所以这可能根本不适用……

我应该提到,使用流行的类似 Java 1.4 的技术 generic programming in go 转换为 interface{} 会遇到与装箱拆箱完全相同的问题(因为这就是我们正在做的事情),除了编译时间类型的丢失安全。对于小类型(如整数),Go 优化了 interface{} 类型,以便转换为 interface{} 的整数列表占用连续的内存区域,并且只占用普通整数的两倍空间。但是,从interface{} 进行转换时仍然存在运行时检查的开销。 Reference.

所有添加通用支持的项目(有几个,而且都很有趣)统一走编译时代码生成的 C++ 路线。

【讨论】:

  • 我对这个困境的解决方案是让 Go 默认设置为“缓慢的执行时间”,并选择在“缓慢的编译器和臃肿的二进制文件”模式下分析程序并重新编译对性能最敏感的部分。太糟糕了,人们实际实现这样的东西往往会走 C++ 路线。
  • 有人提到存储在[]interface{} 中的小类型(即int)使用2 倍于[]int 的RAM。确实如此,即使是更小的类型(即字节)使用高达 16 倍的 RAM 作为[]byte
  • C++ 方法实际上没有两难选择。如果程序员选择编写模板代码,这样做的好处一定会超过慢速编译的成本。否则,他只能用老办法。
  • 难题在于选择哪种方法。如果你通过 C++ 方法解决了这个困境,那么这个困境就解决了。
【解决方案3】:

添加和更新@Vinzenz 和@user7610 的优秀答案。

虽然还不确定,但经过十多年的努力,它看起来 就像参数多态性的设计一样,通俗地说是 被误导性地称为仿制药,将在未来一两年内问世。它 找到一个适合的设计是一个非常困难的问题 现有的语言,感觉好像它属于,但伊恩泰勒投资 大量的精力投入到问题中,看起来像 答案现在触手可及。见https://evrone.com/rob-pike-interview

“类型参数-草案设计”支持使用类型参数,您可以在其中读取处理传入参数的函数,而无需依赖函数声明中指定的类型。见https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md

例如,PrintSlice 函数接收一片整数或字符串并打印出来。见https://www.jetbrains.com/help/go/how-to-use-type-parameters-for-generic-programming.html

package main

import "fmt"

func PrintSlice(type T)(s []T) {
    for _, v := range s {

        fmt.Print(v)
    }
}

func main() {
    PrintSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9})
    PrintSlice([]string{"a", "b", "c", "d"})
}

你可以在这里https://go2goplay.golang.org/p/I09qwKNjxoq 测试这个例子。这个 Playground 就像通常的 Go Playground 一样工作,但它支持通用代码。见https://blog.golang.org/generics-next-step

参数多态性基本上意味着“这个函数或数据结构与任何类型都一样工作”。这就是我们所说的泛型。例如,数组的长度不依赖于数组中的内容。参见https://news.ycombinator.com/item?id=23560798

最早可以将泛型添加到 Go 中的是 Go 1.17 版本,计划于 2021 年 8 月发布。请参阅 https://blog.golang.org/generics-next-step

【讨论】:

【解决方案4】:

其实根据this发帖:

许多人(错误地)得出结论,Go 团队的立场是“Go 永远不会有泛型”。相反,我们了解潜在的泛型具有使 Go 更加灵活和强大以及使 Go 更加复杂的潜力。如果我们要添加泛型,我们希望以一种在尽可能降低复杂性的情况下获得尽可能多的灵活性和强大功能的方式来完成。

【讨论】:

    【解决方案5】:

    参数多态性(泛型)under consideration for Go 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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-12
    • 2010-09-23
    • 2014-08-12
    • 2011-06-21
    • 2019-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多