简化迭代还是引入新的复杂性?
上周,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
评论