大家好,我是煎鱼。
最近有一个社区朋友反馈他在某乎上看到一个提问,内容是如标题,对于 Go 泛型的标识符 不太满意,想看看 Go 以后有没有机会改。
问题上的原话是:
一直对 Go 语言的泛型使用 [] 中括号耿耿于怀,[] 中括号在视觉上很难有辨识度,尤其是和切片在视觉上不具有明显区别,当泛型类型约束是切片时更是灾难,对此一直久久不能释怀,那么 Go 官方在后续版本迭代中是否会考虑新增 来表示泛型,也就是说 [] 和 都能表示泛型,由开发者自己决定采用哪个。
考虑到以前的很多同学没了解过 Go 设计的历史,我又翻了出来。
快速温习
针对泛型而言,我们做一个快速的温习。泛型允许程序员在强类型语言中编写代码时,使用一些以后才确定的类型,其在真正实例化时才会为这些参数指确定类型。
简单来讲,泛型就是参数化多态。其可根据实参类型生成不同的版本,支持任意数量的调用:
func F(a, b T) T{ return a+b }
// T 为 int
F(1, 2)
// T 为 string
F("1", "2")
在编译时期编译器便确定其 T 的入参类型。
为什么不使用尖括号
很多同学在讨论的一个问题,那就是 “为什么 Go 泛型不像 C++ 和 Java 那样使用尖括号?,也出现了 “Go 一直标榜业界工程实践类的榜样,为什么就是不用尖括号” 的言论?
思考问题我们不只看表面,官方说不行,那么我们可以倒推来看,看看 Go 语言就用尖括号:
func print(list []T) {
print(numbers)
print(strings)
print(floats)
普通的函数声明看上去似乎结构清晰,没有什么大问题的。接着往下看:
a := w (z)
我们继续把代码演进一下,简洁一点:
a, b := w (z)
这时候就犯难了,不仅编译器难以解析,人也很难判别,到底指的是:
a := w (z)
又或是:
a, b := w(z)
从上述代码来看,使用尖括号难以分别,因为没有类型信息,就无法确定赋值的右侧是一对表达式 w < x
和 y > (z)
,还是返回两个结果值 w(z)
的泛型函数实例化和调用,其存在歧义。
如果要解决还要引入新的约束,可能会破坏 Go1 的兼容性承诺,这显然是违反 Go 的规范的,在这届核心团队中是无法推进的。
为什么不使用括号
其实最早 Go 泛型的版本是使用了括号的模式,虽然能用,但是用括号会引入新的解析歧义。例如:
var f func(x(T))
从语法上来讲,你无法识别他是未命名参数的 x(T)
函数,还是类型名为参数的 (T)
函数。
同时 Go 语言还存在强制类型转换这一语法,假设代码是 []T(v1)
和 []T(v2){}
,那么你在开括号处,就无法得知其是否代表类型转换。
甚至在函数的完整声明上,我们都会感到困惑:
func F(T any)(v T)(r1, r2 T)
函数入参、泛型、返回值声明均都是括号,造成了语义不清,这显然也是不合理的。
为什么不使用书名号
书名号,也就是:«»
。Go 核心团队表示过,不想使用非 ASCII,未来更没打算支持这类情况。
一些争议
网上也有社区里的同学对不支持 Go 官方的尖括号 的说法不是很认可。
例如,前面提到的 Go 里泛型用尖括号会冲突的这个写法:
v1,v2 := ad
Go 核心团队表示说比较为难。
但网友 @Ninsun 同样甩出了 C++ 泛型的用法下是 OK 的。如下图:
因此有许多社区同学表示,Go 不想用尖括号就是懒得处理,嫌编译器 parser 不好写。
总结
现在来看 Go 已经不太可能在后期将泛型改为 尖括号了。毕竟,米已成炊。泛型也已经有了定论好几年了,尖括号早已被否决。
另外 Go 核心团队显然不希望使用尖括号写法,这样会造成明确语义冲突,也不希望花费大量功夫去重新调整编译器。
还剩唯一一种可能性,那就是 Go 核心团队大换血...(难)