[Golang 1.23 前瞻]使用 Go 实现可组合的函数迭代器

在 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 个,转换成字符串,最后打印出来。

在执行范围循环之前不会执行任何操作,前面所有的定义都是懒加载,因此我们只对数值遍历一次。


往期回顾

#

Golang 中 JSON 操作的 5 个常见陷阱(建议收藏!)

#

使用 Select +Timer 时如何避免内存泄露?

#

使用 Golang 构建你的 LLM APP

#

探索 Go 的 Fan-Out/Fan-In 模式:让并发更 easy

原文始发于微信公众号(Go Official Blog):[Golang 1.23 前瞻]使用 Go 实现可组合的函数迭代器

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/292097.html

(0)
土豆大侠的头像土豆大侠

相关推荐

发表回复

登录后才能评论

评论列表(1条)

  • hsh9的头像
    hsh9 2024年6月22日 下午3:12

    您好,这篇文章让我充分的理解了go的迭代器,但是最后`strIt := Map(g.Filter(rg).Take(20), intToStr)` 是不是应该改成 `strIt := Map(g.Generator().Filter(rg).Take(20), intToStr)`

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!