range-over-func
Background
Discussion-54245: standard iterator interface
关于 Iterator 的提案,包括为什么现在提议这个,以及希望提供的功能。
Discussion-56413: user-defined iteration using range over func values
关于 for range
和 Push/Pull 函数相关 (这个讨论该看)
Discussion-56010: redefining for loop variable semantics
Loop 变量作用域,1.22 已经包含了
Discussion-43557: function values as iterators
Discussion-43557-comment
这两个更早点。也是 Iterator 相关。
关于 1.23 新增的 iter
包及其相关功能。
Simple Summary
大多数语言提供了一种标准化方法来使用迭代器接口迭代存储在容器中的值。
Go 提供了与 map、slice、string、array 和 channel 一起使用的 for range
,但它没有为用户编写的容器提供任何通用机制,也没有提供迭代器接口。
这导致 Go 相关的非泛型迭代器的用法五花八门:
runtime.CallersFrames
返回一个在堆栈帧上迭代的runtime.Frames
;Frames
有一个Next
方法,它返回一个Frame
和一个报告是否有更多帧的 bool 值(也就是下次调用Next
方法是否有值返回)。bufio.Scanner
是一个通过io.Reader
的迭代器,其中Scan
方法前进到下一个值。该值由Bytes
方法返回。错误由Err
方法收集并返回。database/sql.Rows
迭代查询的结果,其中Next
方法前进到下一个值,并且该值由Scan
方法返回。Scan
方法可能会返回错误。archive/tar.Reader.Next
bufio.Reader.ReadByte
bufio.Scanner.Scan
container/ring.Ring.Do
expvar.Do
flag.Visit
go/token.FileSet.Iterate
path/filepath.Walk
sync.Map.Range
部分原因是在引入泛型之前,无法编写描述迭代器的接口。
不过现在有泛型了,我们可以为具有 E
类型元素的容器上的迭代器编写一个接口 Iter[E]
。
其他语言中迭代器的存在表明这是一个强大的工具。
Push/Pull functions
#56413 讨论中关于 push/pull 函数的功能讨论的很多,包括它们的互相转换。
不过和 iter 包现有的并不完全一样,大体概念倒是没变。
Iter Package
目前来说做的不多,两个类型和两个函数签名
1 | type Seq[V any] func(yield func(V) bool) |
slices 和 maps 包中也添加了相关的一些函数,这里随便挑一个看下
1 | // All returns an iterator over index-value pairs in the slice |
用法大概就是:
1 | nums := []int{2,3,4,5} |
尝试写一个吧,比如获取一个 slice 中的奇数:
1 | func Odd[Slice ~[]E, E int](s Slice) iter.Seq2[int, E] { |
这个例子不算太好,因为 v % 2 != 0
这一步约束了这个类型必须是整数,这里我就简单的标注为 E int
了。
现在就可以用了:
1 | nums := []int{2,3,4,5,6} |
但这么写是有点奇怪的,挺奇怪的,我们可以直接操作 yield
函数
1 | slices.All(nums)(func (k, v int) bool { |
然后是 PULL 函数
比如说写一个交换 map k,v 对的函数
1 | func ReplaceKV[K comparable](seq iter.Seq2[K, K]) iter.Seq2[K, K] { |
1 | m := map[string]string{"cn": "CHINA", "en": "ENGLISH", "us":"AMERICA"} |
这里是只用了 slice 和 map 举例,所以更像个语法糖。
应该和结构体或者说复合类型结合起来看,效果会更好,我就不继续写了。
而且这确实是规范了 Iterator 的写法。