写点什么

Go 1.23: 新包 Iter

作者:huizhou92
  • 2024-06-11
    北京
  • 本文字数:2238 字

    阅读完需:约 7 分钟

Go 1.23: 新包 Iter

简化迭代还是引入新的复杂性?


上周,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 来添加了诸如 AllKeys 等方法。这里是它的实现参考:


//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的力量而不损害该语言的基本原则。

参考资料

  1. 61405

  2. 56413

  3. iterators_in_go_123


发布于: 刚刚阅读数: 3
用户头像

huizhou92

关注

种一棵树,最好的时间是十年前,其次是现在 2018-03-09 加入

还未添加个人简介

评论

发布
暂无评论
Go 1.23: 新包 Iter_Go_huizhou92_InfoQ写作社区