Robert 和 Ian 加入我们讨论 Go 中泛型的最新更新。当开发人员开始使用专为试验泛型和 Go 而设计的工具时,他们希望得到什么类型的反馈?讨论泛型的轻量级 Go 论文是怎么回事?为什么我们不能对泛型使用尖括号?
本篇内容是根据2020年7月份#140 The latest on Generics[1]音频录制内容的整理与翻译
过程中为符合中文惯用表达有适当删改, 版权归原作者所有.
Carmen Andoh:欢迎大家收听本期的 Go Time。今天我们有一集非常特别的节目,关于 Go 的泛型的最新进展。今天和我们在一起的是来自 Google Go 团队的 Robert Griesemer 和 Ian Lance Taylor。下午好!
Robert Griesemer:大家好。
Carmen Andoh:还有我们的主持人 Johnny Boursiquot……
Johnny Boursiquot:大家好。
Carmen Andoh:……以及 Jon Calhoun。
Jon Calhoun:嘿,大家好!
Carmen Andoh:今天我来主持,我是 Carmen Andoh。再次欢迎大家收听 Go Time。我们就从更新开始吧。我们有 Ian 在这里,我记得大概是去年十月,你讨论了当时在 GopherCon 上关于模板的演讲……从那时起,一个新的草案提案已经发布了,或许你可以谈谈这个更新是什么,Ian?
Ian Lance Taylor:当然可以。Robert 和我发布了关于泛型的更新设计草案。最大的变化是我们放弃了“契约”(contracts)的概念,而是决定使用接口类型来描述类型参数和类型实参之间的约束,而不是使用单独的语法结构---
契约。很多人看到契约时觉得它们和接口很像,并且很难区分什么时候该用契约,什么时候该用接口……所以我们简化了这个过程---
这几乎完全是 Robert 的功劳---
我们决定只使用接口类型。
接下来我们发布了一个转换工具和类型检查器。我们有一个类型检查器,它可以根据设计草案来工作,这给了我们一定的信心,证明我们写的东西是可行的。
我们还发布了一个转换工具,可以将代码转换为普通的 Go 代码。这个转换工具绝不是最终的版本,它有很多情况还无法处理。它只是一个实验性工具,但它可以让人们实际编写使用泛型的代码并运行它。这样我们就可以了解泛型是否真的对人们有用,是否解决了他们遇到的问题。
Carmen Andoh:你能谈谈你对目前反馈的理解吗?人们在没有泛型或当前草案提案的情况下遇到的主要问题是什么?
Ian Lance Taylor:很多反馈实际上是关于语法的,这既是最不重要的部分,也是最容易理解的部分。当然,拥有一个好的语法很重要,我们也非常关注人们给出的反馈。我们现在在转换工具中实现了两种可能的语法,这是 Robert 做的……
除了语法,还有语义部分……我认为到目前为止,语义部分的反馈总体还是比较正面的。一些人已经尝试了泛型,写了一些相当复杂的代码,我认为我们收到的反馈基本上都是积极的。Robert,你还记得有任何实质性的担忧吗?
Robert Griesemer:不,我认为大部分反馈确实是基于语法问题的,我们试图通过现在的这个替代方案来解决这个问题……我们需要再多测试一下。在语义方面,当然,还有一些地方没有完全敲定。我们在设计草案中也提到了这些,特别是在类型列表方面,例如当类型列表中有类型参数时,这意味着什么。
我们正在进一步完善这些问题,但我不记得在反馈中有哪个问题是主要的阻碍。
Ian Lance Taylor:我们正在努力真正确定类型列表的语义,但我们还没有收到太多这方面的反馈。有人对在结构体或接口中嵌入类型参数的具体细节表示疑惑,这些我们需要进一步决定其确切含义……但实际上,几乎没有人在实际使用泛型时遇到过问题。
Carmen Andoh:说到语义,你们为了改进这个新草案提案,与类型理论专家合作了,包括 Philip Wadler[2],你们还发表了一篇名为《Featherweight Go》[3]的论文,我完全看不懂那篇论文……[笑声] 我为整个 Go 社区悬赏,谁能解密那篇论文。你们还与 Philip[4] 一起参加了一个讨论小组,讨论这篇论文和你们的合作。也许你可以为我们的听众解释一下 Featherweight Go 的本质是什么?你们谁愿意尝试解释一下?
Robert Griesemer:我可以尝试解释一下。这个合作其实是 Rob Pike 找到 Phil Wadler 开始的。他们很早就认识,Phil 对此很感兴趣,我们就开始了讨论。Rob Pike 因为忙没有全程参与,但 Phil、Ian 和我开始就泛型问题进行讨论……他们在类型理论方面有很强的背景。Phil Wadler 多年前为 Java 做过类似的工作,所以他是这方面的专家。
现在我们有一个团队在研究 Featherweight Generic Go,这个语言基于 Go,但大大简化了。它现在只有类型声明和方法,类型声明只有接口和结构体(struct),并且你只能在接口或结构体中定义方法……方法内部只能有简单的函数表达式。所以这是一个非常简化的语言,但你可以调用方法。
在这篇论文中,他探讨了两种情况:一种是 Featherweight Go 的基本形式,没有泛型的 Go;另一种是扩展了泛型功能的 Featherweight Go。这些泛型功能与我们设计草案中的泛型非常相似,不同的是没有类型列表。论文中的类型参数有类型边界(我们现在称之为约束),这些约束也是接口,基本上是对设计草案中泛型设计的简化模型。
这篇论文的目标是证明这个设计是合理的,尤其是我们正在创建的类型系统是健全的,不能编写出违反类型系统的程序。此外,他们还证明了可以通过一种称为单态化的过程,将这种简化的泛型 Go 程序转换为普通的 Go 程序,基本上为每个可能的泛型函数和类型实例化展开所有内容……他们证明这些程序也是可以工作的。
这就是这篇论文的要点,这让我们对设计的合理性非常有信心。我们不会在未来发现一些内部不一致的问题。所以这就是这篇论文带来的好处……
我觉得它也帮助我们更好地理解了将接口作为约束的含义,以及我们如何进行类型检查。所以我认为这是一种很好的协同作用。
Carmen Andoh:很酷。你觉得这种合作帮助你们在语义方面理解得更透彻了吗?这是否对最新的草案提案有直接的影响?
Robert Griesemer:哦,绝对如此。我认为在这篇论文完成之前,我们已经有部分原型在运行了。在这个过程中,我们遇到了各种问题,而我们自己临时发明的解决方案,正好与他们的理论研究平行进行。然后我们相互讨论,尤其是在逐步深入研究论文的过程中,我们能够确认我们的思路与他们的思路是一致的,反之亦然。所以我们的设计虽然是基于直觉和经验,但与他们的数学推导是一致的,这让我们非常高兴,因为这意味着我们没有做一些奇怪的事情。
Phil Wadler 实际上亲自花时间详细向我们解释了这篇论文,这也是我们对这篇论文的理解来源。我不是类型理论专家……所以我现在觉得“好吧,我大概懂了一些数学”,但是---
Ian Lance Taylor:实际上,我的名字也在那篇论文上,但我完全不敢说我理解那篇论文。[笑声]
Carmen Andoh:好吧,大家听到了,不仅仅是你们,连 Ian 和 Robert 对 Featherweight Go 论文及其数学符号也感到非常困难。真有趣。
Ian Lance Taylor:我还要补充一点,他们确实帮助我们从契约转向接口类型。Robert 也推动了我们朝这个方向前进……这让我们更加清楚地认识到接口类型同样强大并且易于使用。
Johnny Boursiquot:值得一提的是,YouTube 上有一段 Phil 实际演示这部分内容的视频……我尝试阅读那篇论文,但进展不大,不过我看了视频,他确实很好地解释了其中的一些关键概念。我听说他们还有计划继续扩展 Featherweight Go 的想法,也许有 [听不清] 或其他实现,我想这些都会基于你们现在的基础,继续探讨“我们还有什么没想到的东西,比如 Rust 的泛型在 Go 中的应用?”是这样吗?
Ian Lance Taylor:是的,完全正确。他们会尝试添加---
正如 Robert 所说,Featherweight Go 非常有限,所以他们会尝试添加 Go 的其他特性,确保类型系统依然健全,泛型系统依然健全。我们认为它会的,但他们的研究结果还是非常值得期待的。
Johnny Boursiquot:你之前提到的一点是,有些反馈是比较表面的,也就是说它们仅限于语法层面;显然,底层还有很多问题需要解决,才能有一个一致的实现。但是为了公平对待这些反馈,很多人已经习惯了使用尖括号来指定泛型类型……大家会疑惑:“为什么一开始用括号,现在又在考虑方括号……为什么我们不能用尖括号?有什么问题?”也许你可以解释一下为什么在 Go 中尖括号会带来麻烦,这或许可以回应一些早期的反馈。
Ian Lance Taylor:Robert 在他发送的关于方括号的邮件中给出了一个很好的例子,说明了在某些情况下,如果你不确定是在看泛型函数或类型,还是在看一对带逗号的表达式(比如某种多重赋值),你确实无法解析尖括号。这本质上是一种语法歧义。Robert,你要不要谈谈解析时不进行类型检查的重要性?
Robert Griesemer:当然。即使在现有的 Go 语言中,也有一些我们在解析时无法确定的情况。经典的例子是当我们有一个带有一个参数的转换或函数调用时。如果你写 f(x)
,这是一个函数调用,还是一个类型转换?我们在解析时不知道。但这没关系,因为我们可以在解析时构建语法树。这是唯一重要的事情。而这棵语法树有一个函子---
可能是一个类型(在类型转换中)---
和一个参数列表。这对函数调用和类型转换都是一样的。在类型检查时,我们可以查看这个函子,看看它是一个类型,还是一个函数。如果是类型,那它就是类型转换;如果是函数,那它就是函数调用。所以这两者都没问题。
问题在于,使用尖括号时,在解析时我们甚至不知道如何创建语法树,尤其是在我们邮件中提到的那个例子中。我们不知道如何解析它,因此也不知道如何构建语法树,这意味着我们无法继续解析。
一种解决方法是,如果我们在解析时有类型信息。在 C++ 等使用尖括号作为模板的语言中,解析器在解析时需要符号信息来做出正确的决定。但这也意味着你在某个地方使用的所有内容必须已经声明了。所以在 C++ 中,必须确保你在表达式中使用的所有东西都已经声明,或者通过某种方式进行前向声明。在 Go 中,我们不能这样做……实际上,我们可以这么做,但我们没有前向声明机制,也不想要它。在 Go 的第一个版本中,确实有前向声明,但我们很快就去掉了它。
在 Go 中,一个包可以跨多个文件存在。如果你在一个文件中引用了一个函数,这个函数可能还没有被声明,可能会在最后一个文件的末尾才出现。所以我们没有办法在解析时拥有这些信息。因此,在没有这种信息的情况下,我们无法解析这些内容……所以尖括号在现有的 Go 中是行不通的。并不是我们不想用它们,而是它们不能在 Go 中工作。
Carmen Andoh:Robert 所提到的,是在 Golang Nuts 讨论组中的一个帖子,这是他们草案提案的最新补充。因为收到了关于语法的反馈,他们增加了括号的选项。现在社区正在反馈他们更喜欢方括号还是括号。
Robert,或者 Ian,你们觉得这两者的权衡是什么?
Ian Lance Taylor:我认为使用括号的好处是,类型参数实际上就是参数,类型实参实际上也是实参……所以使用类似于常规参数的语法来表示类型参数是很有意义的。[听不清] 我觉得这种方式很自然,至少对我来说看起来很顺眼……它读起来也很流畅。缺点是,在一个复杂的泛型函数中,可能会有很多括号飞来飞去,你有类型参数、普通参数、返回参数;它们都是括号包围的列表,有时可能不太容易看清楚发生了什么。
同时,在调用时,有时你传递类型实参,有时却没有,这也会让人有点困惑。例如,如果你有一个 new
函数,它可能会接受一个类型,然后你还需要另一组括号来传递常规参数……这可能会让人感到混淆。
我们还发现了一些使用括号时存在的解析歧义。虽然这些不是很常见的情况,但确实在实际代码中出现过。例如,当你引用一个实例化的类型或函数时,有时难以准确知道发生了什么。
一个简单的例子是结构体中的嵌入字段。你可以在结构体中嵌入一个实例化的类型。在这种情况下,不太清楚你是在嵌入一个类型,还是在执行其他操作。
相比之下,方括号虽然也是列表语法,但类型参数和普通参数的外观不同,这对许多人来说是个优点,但对某些人来说是个缺点……而且目前看起来,使用方括号时遇到的解析歧义要少得多。
目前我还不太清楚 Go 社区的整体偏好……肯定有人喜欢括号,也有人喜欢方括号。但我还没有看到明显的一方胜出。不过我看到很多人表示,无论哪种方式都可以接受,他们没有什么大的反对意见。Robert,你有什么要补充的吗?
Robert Griesemer:不,我觉得这是一个准确的描述。我觉得我们可以自信地说,方括号没有我们在括号中遇到的那些歧义问题。我们在一开始并不知道这些问题,只有在编写代码后才发现……但我们决定继续使用括号,因为我们想在其他方面取得进展。现在提出这个替代方案是因为我们正在重新审视这个决定,以确保我们在最终决定前已经考虑了所有的选项。
Johnny Boursiquot:据我在 Twitter 和其他社交媒体上看到的情况来看,我觉得大家更倾向于方括号。大多数人认为,使用方括号时,代码的含义更加直观,不需要再三确认“这些括号到底对应什么?”我们可以更轻松地理解“哦,这肯定是与泛型类型相关的”,而其他部分则是我们期望的内容。这是我从社区反馈中看到的个人看法。
Robert Griesemer:谢谢你。
Jon Calhoun:我有一个问题……你们有和 IDE 开发者或者语法高亮工具的开发者交流过吗?比如 JetBrains,他们有 GoLand,我想他们或许会对哪种方式在编辑器中更直观有一些反馈……你们有机会和开发这些工具的人交流,听取他们的意见吗?
Ian Lance Taylor:我们有和开发 gopls 语言插件的人交谈过……从他们的角度来看,这并不重要,因为他们只是连接到解析器,而解析器支持两种情况,解析器会将代码的样子反馈给他们……所以他们在添加括号支持时没有遇到太大麻烦,最近他们也在 gopls 中以实验的方式添加了方括号支持……所以至少在这一层面上,这不是问题。不过我们还没有和 JetBrains 交谈过,这确实是个好主意。
Carmen Andoh:对 Go 社区来说,比较实际的问题是---
你们觉得什么时候能收到足够的反馈,进而从草案提案推进到真正的语言变更提案?
Ian Lance Taylor:我们目前没有具体的时间表。如我们之前提到的,我们仍在努力确定一些精确的语义问题,我认为这些不会影响现有的代码;事实上,我确信它不会影响现有的代码。我们想确保我们理解这些问题,并确保多个 Go 编译器能够实现相同的功能。我们还需要有一些关于如何将其添加到语言规范的概念。这是我们目前正在进行的步骤。我们当然会尽快推进,直到正式提出提案。当然,到了那时,这一切都不会是个惊喜。大家已经看过所有的想法了,我们只需要看看它如何表现。到目前为止,我觉得反馈总体上是积极的,这令人鼓舞……但我不确定具体的时间线会是什么样子。
Carmen Andoh:在这个阶段,你们,Robert 和 Ian,希望收到什么样的反馈?
Robert Griesemer:我认为我们想看到那些不工作,但你期望它们工作的情况。我们确实已经看到了一些这样的情况,通常只是我们原型中的一些 bug……我们花了一些时间修复这些 bug,不过我们已经减缓了修复速度,因为这只是个原型,某个时候你得决定在这个上面花多少时间,并在其他地方取得进展……但总的来说,我认为我们想知道“你能否用这个设计写出你期望的泛型代码?”如果不能,为什么不能?请告诉我们。是否有你期望能工作的东西却没有工作?这些问题是否对我们的设计至关重要?如果是,我们是否需要做些什么?
这些是我们需要尽快回答的重要问题,因为一旦我们有了更确定的东西,之后再进行这些更改就会非常困难,甚至是不可能的。
Ian Lance Taylor:是的,我完全同意。我还想说---
是否有让你感到意外的情况?如果你在看使用了泛型的代码,是否有任何地方让你感到困惑,或是代码没有按你预期的方式运行?这种反馈将非常有用。
Jon Calhoun:你们有没有特意避免支持的内容?比如说,其他语言的泛型中有些什么是你们看了之后决定“这不是我们想要支持的”,或者是一些非常小众的用例……?
Ian Lance Taylor:嗯,我最喜欢的例子是 C++ 模板实际上是图灵完备的语言,这真的很酷……但我们决定绝对不支持这种功能,绝对不支持。[笑声]
Carmen Andoh:我知道你们两个都有丰富的泛型使用经验,来自其他语言……Ian,你曾在 Google 的 C++ 可读性团队工作,当时你看到 Go 语言的规范后写了一个编译器……而 Robert,你曾在 V8 团队中编写 Java 的虚拟机……所以从这些经验中,你们是否有一些特别想避免的东西---
我特别想从那些反对泛型的人角度来问,他们认为泛型会增加语言的复杂性。你们在这方面的经验是什么?在这个提案中是如何避免复杂性的?
Robert Griesemer:首先澄清一下---
我在 V8 团队工作过,时间很短,可能不到一年……我在 V8 中真的没有做过任何与泛型相关的事情。我更多是做实现方面的工作。我的泛型经验更多来自 C++ 的模板,可能最高峰是我用 C++ 模板写了一个程序,它可以在编译期间确定一个常量是否是质数……这不是我们想要支持的那种东西。
关于我希望看到或不希望看到的内容---
老实说,我有点担心人们会写出来的代码,这毫无疑问。我们看到了一些人发送给我们的例子,这些例子导致了原型中的崩溃,它们是令人难以置信的复杂,真的很难理解……但正如其他人所指出的,这些人实际上是在挑战极限;他们只是在测试“我能用这个做些什么?”我希望这不是人们以后会写的那种代码。
我认为,如果我们真的发布了泛型,我们首先需要做的事情之一是制定一个最佳实践指南,指导大家如何使用泛型,什么时候应该使用,什么时候不应该使用。类似于我们为 goroutine 和 channel 制定的那些最佳实践;在 Go 的早期,大家使用 goroutine 和 channel 做所有事情,花了一段时间我们才学会哪里合适,哪里不合适。
Ian Lance Taylor:我完全同意。我想补充一点---
在整个多年的过程中,我们一直在努力避免 C++ 中的复杂性,Java 中也有类似的复杂性,虽然程度稍轻……因为这些语言非常强大,同时,它们可能导致人们写出的代码难以理解。这对 Go 来说并不适合。部分原因是因为它们比 Go 更面向对象,语言中内置了继承,因此它们必须在泛型的实现中反映继承……而这又导致了理解“如何选择用于实例化 C++ 模板的确切类型”的复杂性。此外,它们还有函数重载,所以我们还得做重载解析……
这些都是非常强大的技术,可以让人们写出非常紧凑且有效的代码……但同时,初学者进入时,他们根本无法理解到底会使用哪个类型……所以我们尽可能地避免了这种情况。我们希望非常明确地知道泛型函数或类型将如何被实例化,类型参数将是什么。
Johnny Boursiquot:这就像我们在 goroutine 和 channel 的过度使用中看到的情况……在最初确实有点疯狂。但我现在就告诉你们,这种情况肯定会再次发生;会有一波浪潮,大家会想要用泛型做所有事情……然后我们会慢慢开始退回,逐步制定最佳实践。
我真的希望看到 Go 团队在这方面起到一些领导作用,或许可以扩展 Go 博客上的 Effective Go,加入一些关于如何谨慎使用泛型的建议,指出这些功能的最佳使用场景。我相信社区里的很多成员也会站出来,写博客文章,展示应该如何使用和不该如何使用……所以这肯定不会全压在 Go 团队的肩上,但这是大家应该预期到的事情;就像任何新玩具一样,大家都会尝试滥用它……但最终的实现会决定一切。我认为我们会围绕泛型代码的最佳实践进行发展,特别是如何编写适用于生产系统的泛型代码,当你离开后,别人需要接手并理解代码时,这一点非常重要……我希望如此。
Ian Lance Taylor:是的。
Carmen Andoh:我非常喜欢 Go 文化的一点是我们通过文化来管理复杂性,形成了简洁的惯用法,并将这种价值观融入了文化。我认为我们可以继续这样做,推广泛型 Go。这将成为我们是否能控制复杂性的重要部分。这并不一定是技术上的强制,而更多是惯用法/文化上的约束。我们之前在这个节目中讨论过“惯用法即文化”,所以看到它在泛型上也有体现,真的很有趣。
我们有一个前瞻性的问题,假设这个提案被批准并且被纳入语言中……你们是否有计划应对未来对于标准库中集成泛型数据结构和算法的功能请求激增?你们会让这些请求在生态系统中自行发展吗?
Ian Lance Taylor:这是个好问题。目前有没有计划?我想说没有。目前还没有任何计划……但肯定会有计划。实验性的转换工具带有一小部分示例库。我写这些代码时,将它们视为未来可能添加到标准库中的原型。我不认为它们是很好的例子,但我认为它们可以展示我们可能会添加新标准库包的领域,并提供可能的实现供大家审查。我不认为会对现有标准库包进行大量添加。或许会有一些,但大多数现有的标准库包是没有泛型编写的,它们运行得很好。
所以,是的,可能会有很多人说“泛型是什么?”但事实是它们已经可以工作了,我们不需要在那里添加泛型。我认为更多是要新增一些包来真正利用泛型。例如,转换工具有一个 slices
包,它包含了对任何类型的切片操作的各种函数……还有一个 chans
包,它对任何类型的通道进行操作。这类代码目前在 Go 中无法编写,但在泛型中是可以的……所以我觉得这将是我们可能会往标准库中添加的地方。我们肯定不会在这方面快速推进……但你说得对,我们应该制定某种框架来指导如何添加新的包。
Jon Calhoun:我们 Slack 中的一位听众问道:“在你们收集反馈时,有没有一个合适的时间或期望,来衡量编译速度的变化?”尤其是现在这些功能是实验性的,我想大家不应该认为它们的性能就是将来发布时的样子……所以人们应该期待什么,什么时候是反馈的合适时机?
Ian Lance Taylor:这是个好问题。是的,实验工具和任何真实的实现没有任何相似之处。我们知道它很慢,而且肯定会很慢,这是不可避免的……如果这个提案推进并被接受,那么最有可能的实现方式是从主 Go 工具链的一个分支开始,我们会在那个分支上开始添加泛型支持,主要是修改编译器,以及其他需要修改的工具……届时将是开始对编译速度变化进行反馈的合适时机。
我们和一些编译器开发者讨论过,比如 Keith Randall,尤其是他,我们认为可以在不显著增加编译速度的情况下做到这一点。我的意思是,肯定会有一些编译速度的增加;我们认为不会是巨大的增加……但这现在还只是推测。所以,给出反馈的合适时机就是当我们开始进行开发时……希望大家也能够在我们开始在公共分支上工作时贡献力量。
Carmen Andoh:我们可以在 Slack 频道中继续问其他问题……“当泛型发布时,是否会与最佳实践的概念一起发布?”这里他们称之为标准化的集合接口,以避免碎片化……“或者你认为这不会成为问题,比如 size、count 和 length 的差异?”
Ian Lance Taylor:是的,我很难想象 Go 中的标准集合接口到底会是什么样子。显然,C++ 中存在这样的接口,或许我们可以借鉴他们的做法。我不完全确定这会如何运作。但某些事情是相当简单的。我觉得迭代是更复杂的一个问题。C++ 部分因为模板能力的复杂性,能够拥有一个适用于任何集合的泛型迭代接口,而我不确定我们是否能达到那个水平,因为我们的泛型功能不如他们的强大和复杂……不过另一方面,我也不知道是否有人真的详细研究过这个问题,所以也许我们可以在这个领域取得一些进展。
Robert Griesemer:Go 和 C++ 之间也有一些不同的地方,比如 Go 已经有了 map,当然这不是整个集合层次结构,但它确实是一个重要的部分,而 C++ 中是没有 map 的,所以在 Go 中我们经常用 map 解决各种问题,这样做其实很好;而 map 在某种程度上已经是泛型的了。当然,这并不是说它可以替代一个更全面的包……
Ian Lance Taylor:这是个很好的问题。
Robert Griesemer:我觉得很有趣的是,看看人们会用这个做些什么。设计上我们尽量让它与语言的其他部分保持正交性。这意味着在某个地方添加泛型类型参数,会为你能编写的程序增加一个新的维度。所以它真的打开了一个全新的可能性领域,看看大家会怎么利用这一点将会非常有趣。
Carmen Andoh:Slack 中的另一个评论是关于技术上的护栏……目前,govet
和 golint
作为 static checks 非常有用。你们怎么看待在工具链中添加新的检查,来劝阻那些本可以用非泛型方式实现的泛型代码?
Robert Griesemer:嗯,我们可以轻松地让编译器对每个额外的类型参数变慢两倍;这会迅速限制复杂性……
Johnny Boursiquot:[笑声]
Carmen Andoh:这很可能需要通过文化或这些最佳实践和惯用法来实现,对吧?
Robert Griesemer:很容易看到程序中有一个或两个类型参数的函数……但如果超过了两个或三个,你就会开始怀疑“这到底在做什么?这真的有必要吗?这真的好吗?”所以我会说,当你看到这样的代码时,会立刻有一些疑问。但我现在只是即兴发挥;我只是在猜测。我怀疑可能会有一些我们可以说“这不太好”的情况。也许这些东西最终可以进入 vet 检查,但我现在还不知道那会是什么。
Jon Calhoun:我猜类似于 Go Proverbs(Go 箴言)这样的东西会在这方面有所帮助……比如我们有一个箴言是“少量的复制胜过少量的依赖”,我认为如果人们坚持这种心态,如果你只是针对两种不同的类型使用泛型,那还不如复制整个类型或函数。但如果你发现自己需要为 5-6 种不同的类型使用同样的代码,那么泛型可能确实是正确的解决方案。
Robert Griesemer:嗯。
Johnny Boursiquot: 三法则在这里依旧适用…… 我想我们之前在节目中讨论过这个问题;通常我不会考虑抽象或制作通用版本,除非我已经见过至少三种类似的情况。然后我才会开始说,“好吧,我开始看清楚边界了,我开始看到这个东西未来可能会被使用的概率。” 因为任何我们写的代码---
我们只写一次,但可能需要无限期地维护…… 所以我们今天做出的这些决策和我们问自己的这些问题,本质上也是一样的。
但你会看到我们社区里有些人是反对泛型的。我觉得这大概是五五开吧。有些人喜欢Go语言就是因为它没有泛型…… 这是一个非常具体的理由去喜欢一门语言,但也无妨…… 另一些人则是“嘿,如果Go有泛型,我就会用了。” 所以这有点奇怪的分歧,但我觉得最终还是要由整个社区来决定我们希望如何以惯用的方式使用泛型。
Jon Calhoun: 我觉得这也不错,因为如果我们有一个具体的实现可以作为参考,哪怕你只是把它复制到文档或注释里,说“这是这个具体事物的泛型实现”。我觉得那些对泛型不熟悉或编程经验较少的人,看了这些内容后会更容易理解代码。
Carmen Andoh: 我在想,如果泛型真的被加入到语言中,会不会带来我称之为“潜在的Go开发者”的人。目前,我们有忠诚的Go开发者,这些人喜欢并使用Go,或许已经内化了Go没有泛型的这一哲学…… 而成功地将泛型引入语言可能会吸引新的Go开发者,这些人可能是.NET、C++和Java开发者。Robert 和 Ian,你们有什么想法或建议,如何在给他们熟悉的工具的同时,保持Go的特点?
Ian Lance Taylor: 是的,我希望他们能够在适合Go的地方,将他们的编程实践带进来。至于泛型是否会让C++或Java开发者对Go更有吸引力,我不敢确定;但我确实希望如此…… 不过实际情况如何,很难说。毕竟,它仍然是另一种语言。
当然,过去确实有些人因为Go没有泛型而直接拒绝了Go…… 但我不觉得这样的人有很多,我也希望这些人能在泛型推出后重新考虑Go。但从我的角度来看,我希望Go对所有人开放,我希望所有人都能发现Go是一种高效的语言。其实,这并不是说要把其他语言社区的人拉过来…… 我觉得泛型有趣的地方主要是,正如Robert所说,它是正交的;它让我们可以写出以前在Go中无法编写的代码。它让我们可以用一种新的方式解决问题,至少不必再通过类型反射或大量复制来解决问题。
所以能够吸引更多人是很棒的;我希望更多人继续编写Go代码…… 但我主要的兴趣是为大家提供另一种强大的编程工具。至少这是我的主要兴趣。
Robert Griesemer: 是的,我想补充一下,Go现在并没有变成一门“泛型语言”。它只是语言中的另一个机制,就像我们有接口、方法一样…… 这并不意味着你现在必须用面向对象的风格来编写所有的代码。Go一直是多范式的;我们支持不同的编程方式,并鼓励大家根据手头的问题选择合适的方法…… 所以在某些情况下,泛型方法可能是最合适的,那么就尽管使用;如果不是,那就不要使用。
当然,也会有些人特别喜欢玩类型,甚至比让代码如期工作更感兴趣。对他们来说,显然这个新机制是非常有吸引力的。但如果你的目标是最终完成某件事情,那就使用最适合的机制或工具来解决问题。
Johnny Boursiquot: 说到这里,正好是一个很好的过渡,进入我们节目中关于“不受欢迎意见”的环节。
Johnny Boursiquot: 我想请每个人分享一下你们的不受欢迎的意见。这可能与我们讨论的泛型有关,也可能是你在其他地方看到的某些事情,或许你对此有不同的看法。
Jon Calhoun: 如果你觉得难以开口,有些人曾经告诉我们,他们认为纽约市的公交系统比其他交通方式更高效。[笑声] 所以可以是任何话题。
Robert Griesemer: 好吧,那我就先来吧…… 我无法对纽约市的公交系统发表意见;我个人是公共交通的忠实粉丝,尽管现在可能不是推广这个的最佳时机,出于其他原因…… 关于“不受欢迎的意见”---
我不知道这是否算不受欢迎的意见,但我喜欢简短的标识符。我真的喜欢。
Carmen Andoh: [笑]
Robert Griesemer: 我觉得标识符离使用的地方越近,就可以越短。离得越远,标识符就应该越长。 当然也有一些例外,比如在你的包中非常重要和常见的标识符,它可以是单个字母,即使是全局的。最明显的例子可能就是testing.t。
Jon Calhoun: 我不确定你现在还能不能当老师。
Johnny Boursiquot: [笑]
Carmen Andoh: 噢噢…… 为什么,Jon?
Jon Calhoun: 因为每个老师都希望你写非常长的、能自解释的变量名,不论你在哪儿用它们---
至少这是我的经历。我觉得每个老师都希望变量名很长。
Robert Griesemer: 所以如果在一个简单的for循环里,迭代变量叫“index”,我大概会评论一下这个。叫它i,或者j,或者别的什么…… [笑声]
Johnny Boursiquot: Ian,你有什么想法?
Ian Lance Taylor: 好吧,我不知道这是否是不受欢迎的意见,但我觉得我经常说这句话,所以肯定有人没能理解…… 那就是,语言是不完美的,但每次对语言的更改都会伴随巨大的代价。所以当你想为改变语言辩护时---
我们经常看到这样的情况;我敢说每天都有一个关于如何改变Go语言的建议…… 不要只谈论它如何让语言变得更好,还要花点时间讨论它如何让语言变得更糟。因为不存在100%好的语言更改。或者我不该说“没有”,也许它存在。也许还没人想到它。但大概可以肯定的是,所有100%好的语言更改已经被实现了。所以当你想修改语言时,花点时间思考它是如何让事情变得更糟的,同时也思考它如何让事情变得更好。
Johnny Boursiquot: 我觉得Ian刚刚打了个比喻式的麦克风…… [笑声]
Robert Griesemer: 我们现在要回去好好思考一下泛型如何让事情变得更糟…… [笑声]
Ian Lance Taylor: 我觉得已经有很多人告诉我们这一点了……
Jon Calhoun: 我正想说,如果你还没收到这种反馈……
Carmen Andoh: 好吧,这次对话很愉快…… 我代表我的联合主持人Jon和Johnny,感谢Ian和Robert抽出时间与我们讨论关于泛型的新草案提案。
Ian Lance Taylor: 是的。
Robert Griesemer: 我的荣幸。
Ian Lance Taylor: 很有趣,谢谢你们邀请我们。
Jon Calhoun: Carmen,今天我听到你说了很多“如果”。
Carmen Andoh: 什么?
Jon Calhoun: 很多“如果”。
Carmen Andoh: 什么样的“如果”?
Jon Calhoun: “如果这被加入到语言中……”
Carmen Andoh: 嗯,好吧---
我想部分原因是我去年从try中学到的…… 很多时间都花在认真思考并试图解决一个好问题上,但社区中的很多人觉得这是一种既成事实。我大概也在为Robert和Ian说话,当我说“如果”时。我觉得这个草案提案明确地不想显得像是既成事实。
Robert Griesemer: 是的。我想,如果我们从中学到了一件事,那就是我们不能只是拿出一个再怎么成熟的想法,放在那里说“是或否?” 这根本行不通。需要有一个教育过程,就像我们也经历了这种过程一样,我们现在正在通过更开放、更小步伐的方式来实现这一点,争取大家的认同。
这有点奇怪,因为编程语言的演进实际上是一种社会过程。即便你已经“看见了光明”,知道了完美的语言是什么样的,你把它拿出来,也可能没人买账,因为人们无法理解你是如何得出那个结论的。
所以你真的需要让大家一起前进---
有些人可能已经站在你的位置,有些人还没有,但你需要带着大家一起一点点前进,这样我们最终才能达到我们想要的地方…… 这种情况我们在很多事情上都能看到,比如垃圾回收。垃圾回收是在1950年代发明的,最早的Lisp就有垃圾回收机制;那是在1958年,我记得…… 但直到很久以后,它才被主流编程语言接受。也许Java是第一个真正把它推向主流的…… 现在这已经不是什么争议问题了。我觉得很多事情也是如此。
#140 The latest on Generics: https://changelog.com/gotime/140
[2]Philip Wadler: https://github.com/wadler
[3]《Featherweight Go》: https://dl.acm.org/doi/10.1145/3428217
[4]笑声] 我为整个 Go 社区悬赏,谁能解密那篇论文。你们还与 [Philip: https://homepages.inf.ed.ac.uk/wadler/