64 条回复  ·  916 次点击
label 小成 2024-8-19 17:53:22
https://i.imgur.com/7cFYApA.jpeg
xz410236056 小成 2024-8-19 18:26:15
这不就是返回一个闭包(高阶函数)其他语言叫生成器、装饰器。这不是很正常的语法吗?
vipfts 小成 2024-8-19 18:33:03
@Nazz #38 虽然我看不懂, 但给你点赞就对了 https://i.imgur.com/agAJ0Rd.png
lxdlam 小成 2024-8-19 20:20:04
首先是一个时间线:
- 一个 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 trafersal is ofer. That cleanup is made explicit by the stop callback when conferting 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 整个语言的发展历史来看,为了一些之前没考虑到的事情,动态开洞擦屁股这种事,也不是发生一次两次了。但是这条回复的目的还是给大家一个清晰的认知,也建议讨论设计问题前,先搞清楚前因后果时间脉络,减少鸡同鸭讲。
povsister 小成 2024-8-19 20:41:08
#90
概括的非常好了,虽然你可以说 go 是 googelang ,因为他确实也塞了很多私货,甚至按照 k8s 的需求定制特性也不是没有先例。

但观察 go 社区的讨论,你会发现没人像 fshexEX 发言一样:随性发表观点看法而不带任何论据。
甚至 rsc 本人也会对你提出的疑问认真回复(有幸被回复过,只能说大佬看问题的 level 就是不一样)

与其去喷泛型,喷 iter ,不如看看你的使用场景到底有没有必要用到这些特性。
作为一个内部基础库维护者,我真得感谢泛型替我省了不少 garbage 代码和注释的口水,编译器保证比我额外加一堆检查要省事的多,而且 iter 本身,在重数据处理的流程中也是非常好用的特性。

你的业务场景用不到,那就不用。没必要跳出来喷一句:go 的语言设计就是一坨屎山。
如果你有意见,请积极参与到社区标准落地中去,或者,zig ,请。
FYFX 小成 2024-8-19 21:03:18
https://go.dev/wiki/RangefuncExperiment ,OP 应该在一开始就把官方给的例子贴全了
说起来要是我理解的没问题
for i,s := range Backward(sl){...} 这里的 i,s 不是从 Backward 函数返回的,更像是给回调函数声明用的?然后编译器再往回调函数里面补个 return true?
james122333 初学 2024-8-19 21:54:35
我也觉得这过于複杂
jiangzm 小成 2024-8-19 22:32:38
语法过于丑漏
DOLLOR 小成 2024-8-19 23:26:17
把 OP 的 go 翻译成 TS ,大概是这样子的

const Backward = <E>(s: E[]): (myYield: (i: number, e: E) => boolean) => void => {
⬜return (myYield: (i: number, e: E) => boolean) => {
⬜⬜for (let i = s.length - 1; i >= 0; i -= 1) {
⬜⬜⬜if (!(myYield(i, s))) {
⬜⬜⬜⬜return
⬜⬜⬜}
⬜⬜}
⬜⬜return
⬜}
}

const main = () => {
⬜const sl = ["hello", "world", "golang"]
⬜forRange(Backward(sl), (i, s) => {
⬜⬜console.log(`${i} : ${s}`)
⬜⬜return true
⬜})
}

// 模拟 for range 的行为
const forRange = <E>(
⬜iter: (myYield: (i: number, e: E) => boolean) => void,
⬜body: (i: number, s: E) => boolean
) => {
⬜iter(body)
}

main()


也就是说,迭代器并没有增加任何新的语法,就是单纯满足签名为 func(func(int, E) bool)的函数。
你说它繁杂、丑陋嘛,确实,要写大量的模板代码。
但你说它大道至简嘛,确实,没有增加任何新的语法。😂
collery 小成 2024-8-19 23:36:58
不好意思,方法签名数括号没数过来~~~
低智 javaer
返回顶部