Golang 1.18中泛型的使用体验与社区评价
Golang 1.18中泛型的使用体验与社区评价 在 Go 1.18 中,泛型将被引入。社区对此有何反应,泛型在哪些方面可能有用?这是 Go 真正需要的功能吗?
我的真实想法?
Go 终于变得成熟了,即使不是客户的要求,我也可能会考虑使用它……
更多关于Golang 1.18中泛型的使用体验与社区评价的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我不明白这场讨论要走向何方。你想证明什么观点?
为什么要等好几年?我能理解一两年,但好几年?
你确定泛型不是XY问题吗?
我希望Go的作者没有患上健忘症!
是的,这是在泛型到来之后提出的论点,而不是在早期讨论中贬低那些支持 Go 泛型的人的时候 🙂
因为设计泛型有无数种错误的方式。过去的几年对Go团队来说并非等待,而是为了达成一个坚实的设计而辛勤工作的岁月。这个设计更贴近用户,同时在编译时和运行时都保持高效。
这绝对不是XY问题。缺乏泛型会导致代码重复或难以理解的变通方案。这是一个真实存在的问题,而不仅仅是主观感受。
话虽如此,泛型有非常具体的适用场景,不应被滥用于不合适的场景。(也就是说,要避免“手里有锤子,看什么都像钉子”的谬误。)
Dean_Davidson:
我认为有一点我们都能达成共识:最大的“收获”是我们再也不用听人们抱怨缺乏泛型了。
哈哈,完全同意!尽管想抱怨的人总能找到抱怨的东西。Go 语言有很多特性是没有的(并且是故意如此)。总会有人说,“如果 Go 有 X 就好了……”
我不喜欢编写这样的代码
func keysMapT1(m map[string]T1) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func keysMapT2(m map[string]T2) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func keysMapT3(m map[string]T3) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
你好 @aryanmaurya1,
泛型,也称为类型参数,在以下场景中非常有用:
- 用于存储数据但不处理数据的容器类型(例如列表、树等)
- 对不同数据类型执行相同处理的通用函数(可以参考 @mje 的
keysMapTn()函数,它展示了类型参数所解决的问题)
泛型对于任何静态类型语言都是一个有用的补充,但如果设计不当,可能会对开发者的体验、运行时速度、编译速度或这些方面的任意组合产生负面影响。这就是为什么泛型花了这么长时间才在 Go 语言中实现。Go 团队非常谨慎,以避免糟糕的泛型设计中存在的所有陷阱。
例如,可以参阅这篇文章 - 为什么需要泛型? - Go 编程语言 - 特别是“收益与成本”部分。
我个人来自拥有泛型的语言背景,起初确实怀念它们,但后来发现并没有想象中那么离不开。以我的经验来看,Go语言极致的简洁性所带来的好处,完全超过了没有泛型的代价。确实,有些日常工作中泛型能帮上忙,所以它的加入是件好事。但根据我的观点和经验,实际需要泛型的场景,可能比其支持者所宣称的要少,而且它并非一个彻底的革命性变化。因为我代码中复杂的部分通常并不通用;它们往往非常特定。
所以,我的看法是:这很棒,我很高兴看到Go语言在成熟和进步。核心团队采取了一种不急于为语言添加新特性的策略,但一旦添加,通常都是经过深思熟虑的。我个人很喜欢这一点,不过语言设计的一切都是一系列权衡(实际上,等等,生活中的一切不都是一系列权衡吗!)。
当我写100% .NET代码时,同事们会进行友好的竞赛,看谁能用上最多的新语言特性,因为它们发布得太快了,很难跟上。最终,我不得不问自己:“如果我必须挑战自己去使用这个新特性,它真的有那么实用吗?”有些情况下是的(例如LINQ以及投影到匿名类型的能力,就非常有用/强大/符合人体工学,除了增加编译器复杂性外几乎没有缺点),其他情况下可能就不一定了。我仍然喜爱.NET Core团队的工作,我认为对于那些喜欢快速演进语言的人来说,有那样的语言存在是健康的。但对于我目前正在构建的东西来说,Go的表现一直非常出色。
我想有一点我们都能同意:最大的“胜利”是我们再也不用听人们抱怨Go缺少泛型了。
Go 1.18中泛型的引入是Go语言发展的重要里程碑。社区反应总体积极,许多开发者认为这是弥补语言短板的关键一步。泛型主要通过类型参数实现,核心语法包括类型约束和类型推断。
泛型在以下场景特别有用:
- 通用数据结构:如容器、集合、堆栈
- 算法抽象:排序、过滤、映射等通用算法
- 减少重复代码:避免为不同类型编写相似逻辑
示例代码展示泛型的基本使用:
package main
import "fmt"
// 泛型函数示例
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
// 泛型结构体示例
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() T {
if len(s.items) == 0 {
var zero T
return zero
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item
}
func main() {
// 使用泛型函数
intSlice := []int{1, 2, 3}
PrintSlice(intSlice)
stringSlice := []string{"a", "b", "c"}
PrintSlice(stringSlice)
// 使用泛型结构体
stack := Stack[int]{}
stack.Push(1)
stack.Push(2)
fmt.Println(stack.Pop()) // 输出: 2
}
类型约束示例:
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// 使用constraints包定义数值类型约束
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
// 自定义类型约束
type Number interface {
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 |
float32 | float64
}
func Sum[T Number](numbers []T) T {
var total T
for _, n := range numbers {
total += n
}
return total
}
func main() {
fmt.Println(Max(3, 5)) // 输出: 5
fmt.Println(Max(3.14, 2.71)) // 输出: 3.14
ints := []int{1, 2, 3, 4, 5}
fmt.Println(Sum(ints)) // 输出: 15
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(floats)) // 输出: 6.6
}
社区评价方面,大多数开发者认为泛型是必要的,特别是在构建通用库和框架时。反对意见主要集中在复杂性增加和编译时间可能延长的问题上。从技术角度看,Go的泛型实现保持了语言的简洁性,通过类型推断减少了显式类型声明的需要。
泛型确实解决了Go长期存在的痛点,特别是在需要类型安全的通用代码场景中。虽然不是所有项目都需要泛型,但对于库作者和大型项目来说,这是一个重要的工具。


