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

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

在 Rust,Python 或者其他很多语言中,你都可以使用像 filtermap, 甚至 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

 你的肯定是对我最大的鼓励 

Go blog 合集 · 目录
上一篇"go get "和 "go install " 有什么区别?下一篇如何用 golang 从 OpenAI, Ollama 和 Claude 获取可靠的结构化输出?
阅读 1672
 
 
 
 
 
 
 
posted @ 2024-07-03 09:39  技术颜良  阅读(16)  评论(0编辑  收藏  举报