简化迭代还是引入新的复杂性?
上周,Go 1.23 进入冻结期,这意味着不会添加任何新功能,并且任何已添加的功能不太可能被删除。这是一个预览即将发生的变化的好机会。
这篇文章,来了解一下 1.23 转正的 iter 包。
This article is first published in the medium MPP plan. If you are a medium user, please follow me in medium. Thank you very much.
在 Go 1.22 中,引入了range over func实验性功能,但需要通过参数GOEXPERIMENT=rangefunc启用。在 Go 1.23 中,可以直接使用代码实现这种迭代方式。
一个例子
func Backward(s []string) func(yield func(string) bool) { return func(yield func(string) bool) { for i := len(s) - 1; i >= 0; i-- { yield(strings.ToUpper(s[i])) } } } func ToUpperByIter() { sl := []string{"hello", "world", "golang"} for v := range Backward(sl) { // do business } }
复制代码
yield是传递给迭代器的可调用函数的常规名称。
我们考虑一下如何在不使用“iter”包的情况下编写代码来实现相同的功能:
func Convert[S any, D any](src []S, mapFn func(s S) D) []D { r := make([]D, 0, len(src)) for _, i := range src { r = append(r, mapFn(i)) } return r } func ToUpByString() { sl := []string{"hello", "world", "golang"} s0 := Convert(sl, func(v string) string { return strings.ToUpper(v) }) for _, v := range s0 { // do business } }
复制代码
性能对比
➜ huizhou92 git:(master) ✗ go test -bench . -count=3 goos: darwin goarch: arm64 pkg: huizhou92 cpu: Apple M1 Pro BenchmarkToUpByString-10 8568332 128.7 ns/op BenchmarkToUpByString-10 9310351 128.6 ns/op BenchmarkToUpByString-10 9344986 128.5 ns/op BenchmarkToUpByIter-10 12440120 96.22 ns/op BenchmarkToUpByIter-10 12436645 96.25 ns/op BenchmarkToUpByIter-10 12371175 96.64 ns/op PASS ok huizhou92 8.162s
复制代码
结果很明显:ToUpperByIter 性能更好,因为它不会重新分配新的 slice,使得它比以前的方法更高效。
iter 的目标
iter 包旨在提供统一且高效的迭代方法。它为自定义容器类(尤其是在引入泛型之后)提供了标准的迭代接口,并可以替换一些返回切片的现有 API。通过使用迭代器并利用编译器优化,可以提高性能。此外,它还提供了适合函数式编程风格的标准迭代机制。
如何使用 iter
iter支持两种类型的迭代器:
// Seq is an iterator over sequences of individual values. // When called as seq(yield), seq calls yield(v) for each value v in the sequence, // stopping early if yield returns false. type Seq[V any] func(yield func(V) bool) // Seq2 is an iterator over sequences of pairs of values, most commonly key-value pairs. // When called as seq(yield), seq calls yield(k, v) for each pair (k, v) in the sequence, // stopping early if yield returns false. type Seq2[K, V any] func(yield func(K, V) bool)
复制代码
map 包已经使用 iter 来添加了诸如 All 和 Keys 等方法。这里是它的实现参考:
//https://go.googlesource.com/go/blob/c83b1a7013784098c2061ae7be832b2ab7241424/src/maps/iter.go#L12 // All returns an iterator over key-value pairs from m. // The iteration order is not specified and is not guaranteed // to be the same from one call to the next. func All[Map ~map[K]V, K comparable, V any](m Map) iter.Seq2[K, V] { return func(yield func(K, V) bool) { for k, v := range m { if !yield(k, v) { return } } } } // Keys returns an iterator over keys in m. // The iteration order is not specified and is not guaranteed // to be the same from one call to the next. func Keys[Map ~map[K]V, K comparable](m Map) iter.Seq[K] { return func(yield func(K) bool) { for k := range m { if !yield(k) { return } } } }
复制代码
争论
“在我看来,yield 是一个足够复杂的概念,会导致出现大量糟糕的、难以理解的代码。这个建议只提供了语法糖,用于编写语言中已经超出可能范围的内容。我认为这违背了“一个问题 - 一个解决方案”的规则。拜托,让 Go 保持无聊。” 来源
这是社区内常见的反对意见。yield 不容易理解,并且我们可以通过多种方式实现迭代器。
结论
我支持添加iter。
iter包为开发人员提供了许多可能性,旨在简化代码并采用更多的函数式编程实践。然而,由于对性能、复杂性和学习曲线的担忧,它的接受度存在分歧。
与任何新工具一样,关键是在提供明显好处的地方平衡其使用,并同时注意潜在缺点。毫无疑问,Go 社区将继续探索和辩论如何利用iter的力量而不损害该语言的基本原则。
参考资料
61405
56413
iterators_in_go_123
评论