在 Rust,Python 或者其他很多语言中,你都可以使用像 filter
, map
, 甚至 reduce
等函数迭代器,或者将这些函数迭代器组合起来快速实现一些功能。迭代器模式提供了一种对象导向的方式来遍历集合,而循环是一种语言结构,直接在集合上操作。迭代器模式提供了更大的灵活性,而循环更简单且性能更高。需要遍历集合而无需暴露其内部表示时,应该使用迭代器模式。它特别适用于需要延迟求值、统一接口或可重用代码的情况。那么 Golang 如何实现类似的功能呢?Go 1.22版本 引入了 range over func 试验特性,通过GOEXPERIMENT=rangefunc,可以实现函数迭代器。这一特性在 Go 1.23 版本正式开放使用,下面代码可以直接使用 Go 1.23 编译运行。
函数迭代器
函数迭代器是一种可以在 Go 的范围循环中使用的函数。到目前为止,range 循环只能用于一组非常特殊的类型:片、映射、字节、通道、字符串。现在,我们准备实现在任何函数迭代器上进行 range 循环。函数迭代器的签名如下
func(yield func(TYPE)bool)
我们可以为 int 类型创建一个函数迭代器:
func Positives() func(func(int)bool) {
return func(yield func(int)bool) {
for i := 1; i < MaxInt; i++ {
if !yield(i) {
return
}
}
}
}
我们就可以用 range 来迭代它:
for v := range Positives() {
fmt.Println(v)
}
迭代器组合与泛型
假设我们要处理某种集合类型,我希望能对其进行不同的计算,同时保持性能提升(避免重复处理相同的项目)。
我们先定义一个非常基本的类型,然后在此基础上开始构建。
// Iterator is a generic type that can be used in range loops.
type Iterator[A any] func(func(A)bool)
现在,我们可以拥有创建迭代器的具体类型:
// IntGen generates int values
type IntGen struct {
curr int
}
// Generator returns an interator of int values
func (g IntGen) Generator() Iterator[int] {
return func(yield func(int) bool) {
for {
if !yield(g.curr) {
return
}
g.curr++
}
}
}
在这里,我们创建了一种生成 int 值的方法,其使用方法如下:
g := new(IntGen)
for i := range g.Generator() {
fmt.Println(i)
}
我们可以为迭代器类型定义新函数,这样就可以对其进行编译。
// Iterator is a generic type that can be used in range loops.
type Iterator[A any] func(func(A)bool)
func (it Iterator[A]) Take(count int) Iterator[A] {
return func(yield func(int) bool) {
c := 0
for i := range it {
if c >= count || !yield(i) {
return
}
c++
}
}
}
在这里,我们定义了一个函数 Take,它只从迭代器中选择下一个给定数目的项,并将其作为一个新的迭代器返回。
我们来定义一个 filter:
// Filter applies the given filtering function to the iterator.
func (it Iterator[A]) Filter(f func(A) bool) Iterator[A] {
return func(yield func(A) bool) {
for item := range it {
if !f(item) {
continue
}
if !yield(item) {
return
}
}
}
}
Filter 将给定的过滤函数应用于迭代器,并将过滤后的值作为另一个迭代器返回。
也可以实现 map 的功能:
// Map converts Iterator[A] to Iterator[B].
func Map[A, B any](it Iterator[A], mapping func(A)B) Iterator[B] {
return func(yield func(B)bool) {
for item := range it {
if !yield(mapping(item)) {
return
}
}
}
}
此时我们就可以组合几种函数实现更复杂的迭代:
g := new(IntGen)
rg := func(i int) bool { return i >100 && i < 200 }
intToStr := func (i int) string { retung fmt.Sprintf("%v", i)}
strIt := Map(g.Filter(rg).Take(20), intToStr)
for i := range strIt {
fmt.Println(i)
}
上面的代码中,我们创建了一种生成 ints 的方法,然后筛选出 100 到 200 之间的 ints,取其中的前 20 个,转换成字符串,最后打印出来。
在执行范围循环之前不会执行任何操作,前面所有的定义都是懒加载,因此我们只对数值遍历一次。
原文始发于微信公众号(Go Official Blog):[Golang 1.23 前瞻]使用 Go 实现可组合的函数迭代器
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/292097.html
评论列表(1条)
您好,这篇文章让我充分的理解了go的迭代器,但是最后`strIt := Map(g.Filter(rg).Take(20), intToStr)` 是不是应该改成 `strIt := Map(g.Generator().Filter(rg).Take(20), intToStr)`