首先是一个时间线:
- 一个 standard 的 iterator 是一个社区一直在讨论跟推进的,正式的 proposal 在 2022 年就已经成型并进行了广泛讨论:
https://github.com/golang/go/discussions/54245 。
- rsc 选择了使用 push-func 来实现的讨论,针对于他对*既有* stdlib 的代码的观察
https://github.com/golang/go/discussions/56413 。
- 关于最终加入 spec 的讨论,从 go1.22 到 1.23 release ,数百条关于实现方式、问题、语法选择的讨论都可以在
https://github.com/golang/go/issues/61405 看到。
在这条帖子的讨论中,mix 了非常多的对于不同方向的讨论:
- 该不该加入 Iterator ?这个问题相信毋庸置疑,一个标准化的 iterator 定义能够极大方便用户和库开发者。举个非常简单的例子,我们可以将 `sql.Rows` 传入一个 `slices.Chunk` 函数中,使得只需要
```
for batch := range slices.Chunk(sql.Rows() ,6) {
// do something with current batch
}
````
就能非常简单将 sql 返回的结果分批处理。类似的场景在各种延迟计算、数据获取、基于数据流向的调度中非常常见。
- 该不该使用 push-func 实现? push-func 跟 pull-func 虽然在表达能力上是一致的,但是 rsc 认为:
> Although push and pull functions are duals, they have important differences. Push functions are easier to write and somewhat more powerful to invoke, because they can store state on the stack and can automatically clean up when the traversal is over. That cleanup is made explicit by the stop callback when converting to the pull form.
push-func 更容易转成 pull-func ,且更容易实现。当然,直观程度上,push-func 是弱于 pull-func 的,这导致了这次语法看起来更加奇怪。(来自于 Java 的例子是,stdlib 对 iterator 的实现是 pull based 的:
https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/Iterator.html )
- 该不该使用 rangefunc 实现 iterator ?这个是社区最核心的讨论点之一。援引一个老哥的文章:
https://www.gingerbill.org/article/2024/06/17/go-iterator-design/,社区核心始终认为,这种函数破坏了既有用户群体对这个语言的 philosophy (宗旨?方法论?不知道怎么去定义这个词比较好)的认知,一部分群体认为 Go 就是个基于过程的语言,不应该如此“函数式”;同样也有人认为,这种基于完全隐式的控制流(依赖 function stack frame 控制生命周期,range Func 完全依靠 compiler 重写)是引入新的 Bug 的万恶之源。
就我个人来说,我并不赞成使用这种方式实现,如果有一个编译器可以认知的新的 iterator 对象,那无论从实现还是使用角度都会简单很多,而且就 go 整个语言的发展历史来看,为了一些之前没考虑到的事情,动态开洞擦屁股这种事,也不是发生一次两次了。但是这条回复的目的还是给大家一个清晰的认知,也建议讨论设计问题前,先搞清楚前因后果时间脉络,减少鸡同鸭讲。