当前位置: 首页 > 科技观察

为什么Go的泛型一拖再拖?

时间:2023-03-16 19:04:27 科技观察

大家好,我是从不放鸽子的炸鱼。前段时间,关于Go语言泛型的讨论频频出现在各个微信群,并冲上了国内外各大文章的“头条”:根据信息汇总,Go泛型将在近几年发布,但总的来说,GoGenerics一拖再拖。好家伙,刚开始知道的时候考虑Go1.16的发布,后来推到Go1.17,现在推迟到Go1.18(2021年底)。看到表面的信息后,泛型“东西”突然火起来的原因之一是Go官方11岁[1]的博文点燃了它。同时,在最近的GopherCon2020大会上,RobertGriesemer分享了Typing[Generic]Go。更正式地说,Go泛型更面向大众,也表明官方认为它已经到了一个新的阶段,进入了最后的实施阶段。废话不多说,既然官方已经在摩拳擦掌,我们的学习之路也得跟上,所以本文就介绍一下Go泛型的现状,在介绍的过程中通过不断的思考,最终得出一个理由。什么是泛型泛型编程是一种编程语言的风格或范例。泛型允许程序员在用强类型语言编写代码时使用一些后来确定的类型,而它在实际实例化时不会为这些参数指定类型。另外,每种语言、其编译器、运行环境对泛型的支持不同,需要辩证。简单来说,泛型就是参数多态。可以根据实际参数类型生成不同的版本,支持任意多次调用:funcF(a,bT)T{returna+b}//TisintF(1,2)//TisstringF("1"","2")编译器在编译时确定T的输入参数类型。这也是Go的泛型实现“编译时类型安全”的要求之一。为什么需要泛型这时候可能有人会说不用泛型也行...我感觉写业务代码没什么作用,比起泛型还是把错误做好(针对具体的newnews,请参考:Heavy:Goerrorswillnotanyplansforfurtherimprovement)。但是,泛型有其自身所需的场景。最常见的就是基础库在处理和获取配置中心数据的时候,必须要对类型进行处理。经常会遇到下面的场景:手写一个“泛型”如果用接口(interface)按类型来做,就得切换(type)来枚举所有的基本类型。这显然是不合理的,也不可能做太复杂的逻辑,支持的类型还是泄露了。另外,单从语言层面来说,支持泛型是必然事件,因为泛型的存在对于解决特定领域的问题具有一定的意义。接口和泛型有什么区别?上面我们提到了接口类型。这时候泛型的第二个经典问题出现了。那就是“接口和泛型有什么区别?”为什么不用一个接口来实现“泛型”:typeTinterface{...}funcF(a,bT)T{returna+b}是这样的,但是在这里面有个致命的缺陷。即接口的输入输出参数在运行时可以表示为不同的类型:F("Friedfish",233)一定要做好,必须在内部断言参数,否则就是字符串类型friedfish而怎么把它加到int类型的233上,难免会报错。另一方面,从“泛型”的实际使用来看,编译器会保证泛型函数的输入输出参数必须是同一类型,并且有强制检查://Error:typecheckingfailedformmainF("Friedfish",233)//必须是同一类型才能正常运行F(666,233)两者有本质区别。泛型会更安全,可以确保在编译的早期发现错误,而不是等到运行时(并且可能存在隐藏的错误)。一般来说,泛型比接口有以下优点:更安全:可以在编译的早期发现错误。良好的性能:静态类型。过去:为什么这么长时间没有仿制药?在社区微信群里,看到有朋友在??抱怨“Go语言没有泛型?”。变相的看,可能会想“Go已经11岁了,2020年就没有泛型了?”。这显然是错误的,因为泛型在本质上并不是绝对必要的,更不是Go语言的早期目标,所以在过去的发展阶段,他们并没有过多地关注这一点,而是将重点放在了其他特性上。另外,Go语言过去其实也进行过大量的泛型proposal实验。基本时间线(via@changkun)如下:虽然偶尔有中断,但仔细一看,我在2010年试过,现在2020年了。也很励志,显然官方也在进行中寻找方法和尝试,但一直没有找到更好的解决方案,大部分都存在问题,社区对解决方案的争论不断。现在:GoGenerics有两种方法可以尝试泛型。在线IanLanceTaylor提供了一个在线编译的go2go[2]:另一个是离线的,即在本地安装Go的特定分支版本:$gitclonehttps://github.com/golang/go$gitcheckoutdev.go2go$cdsrc&&。/all.bash不过这种本地安装的方式会比较费时间。初次尝试,推荐使用go2go。在尝鲜中,可以看到代码块中声明了一个Print方法,其函数签名体分为三部分:乍一看函数签名,变量T的关键字any是什么?你可能听说过早期泛型说到契约,这是契约吗?其实严格来说并不是,因为为了进一步简化语法,2020.06.07官方已经移除了contract。它已经被改造,现在只需要编写参数化接口。上面的any关键字是一个预定义的类型约束,它允许在声明后将任何类型用作类型参数,并允许函数用于任何类型的操作。从语法分析来看,Print方法包含以下属性(从左到右):类型列表:将输入参数的类型列表声明为一个T变量,可以传递任何类型的参数。形参列表:声明入参的形参列表是T变量的一个切片,形参为s。返回类型列表:声明函数的返回参数列表。上面的函数签名是Go泛型的基本外观。由于本文不是CRUD泛型,案例就不展开了(现有的泛型还不成熟)。如果你有兴趣,我强烈推荐阅读提案:TypeParameters-DraftDesign[3]。为什么不在通用战争中使用尖括号?社区很多同学都在讨论的一个问题是“为什么Go泛型不像C++和Java那样使用尖括号?”还出现了“Go一直是业界工程实践的典范,你为什么不直接用尖括号”?我们思考问题的时候不要只看表面,官方说没有,那么我们可以回过头来看,Go语言使用尖括号:funcprint(list[]T){print(numbers)print(strings)print(floats)普通函数declarations看起来结构清晰,没有什么大问题,接着往下看:a:=w(z)下面继续进化代码,让它更简洁:a,b:=w(z)这个时候就难了,不仅编译器很难解析,人类也很难区分,它指的是什么?:a:=w(z)or:a,b:=w(z)从上面的代码来看,使用尖括号很难区分,因为没有类型信息,无法判断赋值的右边是不是边是一对表达式w(z),或者返回两个结果值w(z)的泛型函数的实例化和调用,存在歧义。为了解决它,将引入新的约束,这将破坏Go1的兼容性承诺,这显然是不合理的。为什么不使用括号?事实上,最早版本的Go泛型使用了括号。虽然可以使用,但是使用括号会引入新的解析歧义。例如:varffunc(x(T))从语法上看,你无法区分它是带未命名参数的x(T)函数还是带命名参数的(T)函数。同时Go语言也有强制类型转换的语法,假设代码是[]T(v1)和[]T(v2){},那么你无从知晓是否代表类型转换在开括号处。甚至在函数的完整声明中,我们也会感到困惑:funcF(Tany)(vT)(r1,r2T)函数入参、泛型、返回值声明都在括号内,导致语义不清,显然不合理.为什么不用书名(??)想的漂亮,不想用非ASCII以后不打算支持了。综上所述,在这篇文章中,我们从多个维度介绍了Go泛型的相关内容,了解了上一期Go泛型再次火爆的信息来源是什么。我也知道Go泛型是什么以及它们与接口之间的区别。同时,我们也对一些业界常见的问题进行了解释和说明,例如接口和泛型的区别、泛型的历史、尖括号/括号/泛型的书名之争。最后回答一下最初的问题“为什么Go的泛型一拖再拖”,主要是:Go语言早期的目标(工作重心)不是泛型。Go语言从2010年到2020年断断续续地在做Go泛型的提案,但总是“失败”,也在不断吸取经验。Go语言社区的反馈确实很多。关于使用什么符号来表示泛型存在着无休止的争论。Go语言的泛型还不成熟,很多细节都没有很好的支持。显然,Go官方在保证Go1向后兼容性的同时,并不想直接妥协一个随便的方案,所以一直在改进。随着Go语言在业界的不断应用,泛型也像错误一样被推到了风口浪尖。Go泛型什么时候发布?前段时间还从欧神(@changkun)处获悉:在GopherCon2020GoTeamAMA期间,russcox谈到了相关问题,表示到明年年底,将能够成为行政目标现场有生产环境的试用版。但真正的时间线肯定要看泛型的实现者:robert和keith,大家可以多关注他们,可以得到第一手的资料,而且可以肯定的是,Go泛型在明年2月之前无法上线试用的版本。最后,灵魂拷问:大家对Go语言的泛型有什么想法和看法,欢迎留言一起讨论。