如何看待go语言泛型的最新设计?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何看待go语言泛型的最新设计?相关的知识,希望对你有一定的参考价值。
Go 由于不支持泛型而臭名昭著,但最近,泛型已接近成为现实。Go 团队实施了一个看起来比较稳定的设计草案,并且正以源到源翻译器原型的形式获得关注。本文讲述的是泛型的最新设计,以及如何自己尝试泛型。
例子
FIFO Stack
假设你要创建一个先进先出堆栈。没有泛型,你可能会这样实现:
type Stack []interfacefunc (s Stack) Peek() interface
return s[len(s)-1]
func (s *Stack) Pop()
*s = (*s)[:
len(*s)-1]
func (s *Stack) Push(value interface)
*s =
append(*s, value)
但是,这里存在一个问题:每当你 Peek 项时,都必须使用类型断言将其从 interface 转换为你需要的类型。如果你的堆栈是 *MyObject 的堆栈,则意味着很多 s.Peek().(*MyObject)这样的代码。这不仅让人眼花缭乱,而且还可能引发错误。比如忘记 * 怎么办?或者如果您输入错误的类型怎么办?s.Push(MyObject)` 可以顺利编译,而且你可能不会发现到自己的错误,直到它影响到你的整个服务为止。
通常,使用 interface 是相对危险的。使用更多受限制的类型总是更安全,因为可以在编译时而不是运行时发现问题。
泛型通过允许类型具有类型参数来解决此问题:
type Stack(type T) []Tfunc (s Stack(T)) Peek() T
return s[len(s)-1]
func (s *Stack(T)) Pop()
*s = (*s)[:
len(*s)-1]
func (s *Stack(T)) Push(value T)
*s =
append(*s, value)
这会向 Stack 添加一个类型参数,从而完全不需要 interface。现在,当你使用 Peek() 时,返回的值已经是原始类型,并且没有机会返回错误的值类型。这种方式更安全,更容易使用。(译注:就是看起来更丑陋,^-^)
此外,泛型代码通常更易于编译器优化,从而获得更好的性能(以二进制大小为代价)。如果我们对上面的非泛型代码和泛型代码进行基准测试,我们可以看到区别:
type MyObject struct
X
int
var sink MyObjectfunc BenchmarkGo1(b *testing.B)
for i := 0; i < b.N; i++
var s Stack
s.Push(MyObject)
s.Push(MyObject)
s.Pop()
sink = s.Peek().(MyObject)
func BenchmarkGo2(b *testing.B)
for i := 0; i < b.N; i++
var s Stack(MyObject)
s.Push(MyObject)
s.Push(MyObject)
s.Pop()
sink = s.Peek()
结果:
BenchmarkGo1BenchmarkGo1-16 12837528 87.0 ns/op 48 B/op 2 allocs/opBenchmarkGo2BenchmarkGo2-16 28406479 41.9 ns/op 24 B/op 2 allocs/op
在这种情况下,我们分配更少的内存,同时泛型的速度是非泛型的两倍。
合约(Contracts)
上面的堆栈示例适用于任何类型。但是,在许多情况下,你需要编写仅适用于具有某些特征的类型的代码。例如,你可能希望堆栈要求类型实现 String() 函数
参考技术A 莹本文最初发布于 Go 语言官方博客,由 InfoQ 中文站翻译并分享。
近日,Go 语言社区正式发布 2019 年度调查报告,本次调研共收到 10975 份回复,几乎是 2018 年的两倍,以下是本次报告的重要内容。
重要结论
本文篇幅较长,希望快速了解结果的可以阅读如下概述:
调查对象的人口统计数据与 Stack Overflow 相似,因此这些结果代表了广泛的 Go 开发者;
大多数受访者每天都使用 Go,而且这个数量每年都在上升;
Go 的使用仍主要集中在科技公司,但也越来越多地出现在金融和媒体等行业;
通过调整方法,我们看到大多数年度同期指标都很稳定,并且高过我们之前的认知;
受访者使用 Go 解决的问题类似,特别是构建 API/RPC 服务和 CLI,而这与所在组织规模无关;
大多数团队都会设法更新当前 Go 语言的版本,当第三方提供商不能支持 Go 的当前版本时,就会妨碍开发人员采用;
在 Go 生态系统中,几乎每个人都在使用 Modules,但在包管理方面仍然存在一些混乱;
具有高优先级的改进项包括改进开发人员的调试体验、Modules 使用体验和云服务使用体验;
VS Code 和 GoLand 使用率持续上升,四分之三的受访者当下首选二者;
泛型依旧被认为是 Go 缺失的关键特性。
详细说明
2019 年,我们提出了一些新问题,以帮助更好地了解参与调查的人,比如从事编程的经验年限和所服务的组织规模,这些问题参考了 StackOverflow 的年度调查。我们也看到最终结果与 StackOverflow 2019 年的结果非常接近。我们的结论是,本次调查的受访者与 StackOverflow 的受访者经验水平类似,而且不同规模的组织比例也相似(差别是我们的调查对象是 Go 开发人员)。
所在组织规模
根据调查,大部分受访者所在公司的人数规模在 1000 人以下,且其中有 65% 的受访者具备 10 年以下编程经验。
从事编程工作(或编程作为工作的一部分内容)的时间
Go 开发者相对较新,半数经验不足两年
从结果来看,大多数受访者(56%)都是相对较新的 Go 开发者,他们使用 Go 还不到两年。另外,72% 的受访者在工作时间使用 Go 参考技术B 目前,为实现泛型的需求,在Go语言中往往有如下几种方式1[1]:
1.Interface (with method) 优点:无需三方库,代码干净而且通用。 缺点:需要一些额外的代码量,以及也许没那么夸张的运行时开销。2.Use type assertions 优点:无需三方库,代码干净。 缺点:需要执行类型断言,接口转换的运行时开销,没有编译时类型检查。3.Reflection 优点:干净 缺点:相当大的运行时开销,没有编译时类型检查。4.Code generation 优点:非常干净的代码(取决工具),编译时类型检查(有些工具甚至允许编写针对通用代码模板的测试),没有运行时开销。 缺点:构建需要第三方工具,如果一个模板为不同的目标类型多次实例化,编译后二进制文件较大。
betterGo就是通过code generation来实现泛型
如何使用
如果你想使用betterGo来通过自动生成代码的方式实现泛型,可以看下面的例子:
在项目中包含了测试用例,例如,需要使用泛型的代码是test/map/map.go,如果想用interface 的函数就是enum.Map 这样子用。
如果想生成具体类型的函数,就运行这行命令:go run main.go -w -f test/map/map.go
然后你发现 test/map/map.go 改变了,enum.Map 变成了: enum.MapOriginFn(origin, fn)
然后你看项目目录下生成了: utils/enum/map.go,就是具体类型的函数
参与项目
如果想和我们一起完成项目的开发,可以直接看代码,然后看到ast相关的包,就简单进去看看,猜猜什么意思,应该就可以理解这个项目以及代码了。
如果想从理论出发的话,可以简单看看这本书:https://github.com/chai2010/go-ast-book ,其实他也就是把 ast 包里的代码简单讲讲。
想参与具体开发的话,又没有想改进的地方,可以看看项目接下来的TODO List 参考技术C 1、学习曲线容易
Go语言语法简单,包含了类C语法。因为Go语言容易学习,所以一个普通的大学生花几个星期就能写出来可以上手的、高性能的应用。在国内大家都追求快,这也是为什么国内Go流行的原因之一。
2、效率:快速的编译时间,开发效率和运行效率高
开发过程中相较于 Java 和 C++呆滞的编译速度,Go 的快速编译时间是一个主要的效率优势。Go拥有接近C的运行效率和接近php的开发效率。
3、出身名门、血统纯正
之所以说Go出身名门,从Go语言的创造者就可见端倪,Go语言绝对血统纯正。其次Go语言出自Google公司,Google在业界的知名度和实力自然不用多说。Google公司聚集了一批牛人,在各种编程语言称雄争霸的局面下推出新的编程语言,自然有它的战略考虑。而且从Go语言的发展态势来看,Google对它这个新的宠儿还是很看重的,Go自然有一个良好的发展前途。
4、自由高效:组合的思想、无侵入式的接口
Go语言可以说是开发效率和运行效率二者的完美融合,天生的并发编程支持。Go语言支持当前所有的编程范式,包括过程式编程、面向对象编程、面向接口编程、函数式编程。程序员们可以各取所需、自由组合、想怎么玩就怎么玩。
5、强大的标准库
这包括互联网应用、系统编程和网络编程。Go里面的标准库基本上已经是非常稳定了,特别是我这里提到的三个,网络层、系统层的库非常实用。Go 语言的 lib 库麻雀虽小五脏俱全。Go 语言的 lib 库中基本上有绝大多数常用的库,虽然有些库还不是很好,但我觉得不是问题,因为我相信在未来的发展中会把这些问题解决掉。
6、部署方便:二进制文件,Copy部署
这一点是很多人选择Go的最大理由,因为部署太方便了,所以现在也有很多人用Go开发运维程序。 参考技术D 像 C# 和 Java 也可以使用 unsafe 来访问更底层,而高级封装,Go 语言只是抽象了一些用 C 实现起来特别繁重,坑特别多的东西.就像 slice 简化了对数组的操作和处理,而 channel 什么的,让实现并发逻辑简洁又高效,让程序员可以有更多精力聚焦业务逻辑的设计,而不是关心这个锁,那个锁.但要说到语言设计的优劣,Go语言确实没太多亮点.特别是处理数据库数据和 JSON类似的数据还是和其他强类型语言一样,麻烦又繁琐.
但在工程上,或者实际项目上,它有无可匹敌的几大优势:
1. 容易部署,比任何一种能胜任商业项目的语言都要简单,干练.
2. 由于语言设计的硬性规则,让执行一套辅助开发的工具,更好实现.比如代码格式化,代码分析.(虽然有点没人性,但很适合发挥团队效益)
3. 也因为硬性规则,编译时间特别快.
4. 也因为硬性规则,单元测试起来也很方便,基本可以实现边写边测.这种特性有时候很有用.
5. 因为编译时间快和部署的相对简单,它也能像动态语言一样,做一些类似脚本的工作.不需要像 Java 和 C# 一样,做点小事情,也要一个硕大的运行库,什么都要正规正矩的设计几个接口,不然重用起来很难.
再者很少有大项目,不需要或多或少的触碰一下底层来突破性能瓶颈,或者加速项目开发的.
比如说在 Go 语言里, 可以用 unsafe.Pointer(不需在内存上拷贝数据) 在 []byte 和 string 之间进行转换.
总而言之,Go 语言是一种进可攻退可守的语言.可以偏向效率的很快开发一个项目,可以为了性能,不断的优化数据结构,不断的开发硬件的性能.
以上是关于如何看待go语言泛型的最新设计?的主要内容,如果未能解决你的问题,请参考以下文章