Go语言泛型迭代器设计:range-over-func机制与自定义集合遍历实现

Go 1.23 range-over-func特性概述

Go 1.23正式引入了range-over-func语法,允许在for-range循环中直接遍历函数类型的值。这一特性为Go语言自定义集合遍历提供了标准化的迭代器协议,解决了长期以来Go缺乏统一迭代器抽象的问题。

range-over-func支持三种函数签名作为range目标:

// 1. 单值yield函数:func(yield func(V) bool)
// 2. 双值yield函数:func(yield func(K, V) bool)
// 3. 无返回值push函数:func(yield func() bool)

yield函数返回bool表示调用方是否希望继续迭代,返回false时迭代器提前终止。这一设计使得迭代器可以按需计算,无需预先物化整个集合。

自定义集合迭代器实现

以一个过滤切片为例,实现一个只返回满足条件元素的迭代器:

package main

import "fmt"

// FilterFunc 定义过滤条件
type FilterFunc[T any] func(T) bool

// Filter 返回一个迭代器函数,只yield满足条件的元素
func Filter[T any](slice []T, f FilterFunc[T]) func(yield func(T) bool) {
    return func(yield func(T) bool) {
        for _, v := range slice {
            if f(v) {
                if !yield(v) {
                    return // 调用方break,提前终止
                }
            }
        }
    }
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // 使用range-over-func遍历偶数
    for v := range Filter(nums, func(n int) bool {
        return n%2 == 0
    }) {
        fmt.Println(v) // 输出: 2, 4, 6, 8, 10
    }
}

二叉树前序遍历迭代器实现

利用range-over-func可以优雅地实现树结构的延迟遍历,无需预先将节点收集到切片中:

package main

import "fmt"

type TreeNode[T any] struct {
    Value T
    Left  *TreeNode[T]
    Right *TreeNode[T]
}

// PreOrder 前序遍历迭代器
func (n *TreeNode[T]) PreOrder() func(yield func(T) bool) {
    return func(yield func(T) bool) {
        var traverse func(node *TreeNode[T]) bool
        traverse = func(node *TreeNode[T]) bool {
            if node == nil {
                return true // 继续遍历
            }
            if !yield(node.Value) {
                return false // 调用方break
            }
            if !traverse(node.Left) {
                return false
            }
            return traverse(node.Right)
        }
        traverse(n)
    }
}

func main() {
    root := &TreeNode[int]{
        Value: 1,
        Left:  &TreeNode[int]{Value: 2, Left: &TreeNode[int]{Value: 4}},
        Right: &TreeNode[int]{Value: 3, Right: &TreeNode[int]{Value: 5}},
    }

    // 遍历二叉树,找到第一个大于3的值就停止
    for v := range root.PreOrder() {
        fmt.Printf("visiting %d\n", v)
        if v > 3 {
            fmt.Printf("found: %d, stopping\n", v)
            break // yield返回false,迭代器立即终止
        }
    }
    // 输出:
    // visiting 1
    // visiting 2
    // visiting 4
    // found: 4, stopping
}

Map键值对迭代器与双值yield

当需要同时返回键和值时,使用双值yield函数签名func(yield func(K, V) bool)

package main

import (
    "fmt"
    "sort"
)

// OrderedMap 保持插入顺序的Map
type OrderedMap[K comparable, V any] struct {
    keys []K
    data map[K]V
}

func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
    return &OrderedMap[K, V]{data: make(map[K]V)}
}

func (m *OrderedMap[K, V]) Set(key K, value V) {
    if _, exists := m.data[key]; !exists {
        m.keys = append(m.keys, key)
    }
    m.data[key] = value
}

// All 返回双值迭代器,按插入顺序遍历
func (m *OrderedMap[K, V]) All() func(yield func(K, V) bool) {
    return func(yield func(K, V) bool) {
        for _, k := range m.keys {
            if !yield(k, m.data[k]) {
                return
            }
        }
    }
}

// SortedKeys 返回按键排序的迭代器(需K满足cmp.Ordered约束)
func (m *OrderedMap[K, V]) SortedKeys() func(yield func(K, V) bool) {
    // 仅当K实现cmp.Ordered时可用
    // 此处为示意,实际需更细粒度的类型约束
}

func main() {
    m := NewOrderedMap[string, int]()
    m.Set("banana", 3)
    m.Set("apple", 1)
    m.Set("cherry", 2)

    // 按插入顺序遍历
    for k, v := range m.All() {
        fmt.Printf("%s: %d\n", k, v)
    }
    // 输出:
    // banana: 3
    // apple: 1
    // cherry: 2
}

迭代器组合与管道模式

迭代器函数可以嵌套组合,形成类似函数式编程的管道操作链。每个操作接收一个迭代器,返回一个新的迭代器:

package main

import "fmt"

// Seq 泛型迭代器类型
type Seq[V any] func(yield func(V) bool)

// Map 对每个元素应用转换函数
func Map[V, W any](seq Seq[V], f func(V) W) Seq[W] {
    return func(yield func(W) bool) {
        for v := range seq {
            if !yield(f(v)) {
                return
            }
        }
    }
}

// Filter 过滤元素
func Filter[V any](seq Seq[V], f func(V) bool) Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if f(v) && !yield(v) {
                return
            }
        }
    }
}

// Take 取前N个元素
func Take[V any](seq Seq[V], n int) Seq[V] {
    return func(yield func(V) bool) {
        count := 0
        for v := range seq {
            if count >= n {
                return
            }
            if !yield(v) {
                return
            }
            count++
        }
    }
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // 管道:过滤偶数 -> 平方 -> 取前3个
    seq := Take(
        Map(
            Filter(
                func(yield func(int) bool) {
                    for _, v := range nums {
                        if !yield(v) {
                            return
                        }
                    }
                },
                func(n int) bool { return n%2 == 0 },
            ),
            func(n int) int { return n * n },
        ),
        3,
    )

    for v := range seq {
        fmt.Println(v) // 输出: 4, 16, 36
    }
}

iter标准库包与Pull迭代器模式

Go 1.23的标准库iter包提供了iter.Seq[V]iter.Seq2[K, V]类型别名,以及Pull模式转换函数iter.Pull,将Push模式(yield回调)转换为Pull模式(next/stop函数对):

package main

import (
    "fmt"
    "iter"
)

func main() {
    // 创建一个迭代器
    seq := func(yield func(int) bool) {
        for i := 1; i <= 10; i++ {
            if !yield(i) {
                return
            }
        }
    }

    // 转换为Pull模式
    next, stop := iter.Pull(seq)
    defer stop() // 确保资源释放

    // 手动迭代
    for {
        v, ok := next()
        if !ok {
            break
        }
        if v%3 == 0 {
            fmt.Printf("found multiple of 3: %d\n", v)
            continue
        }
    }
    // 输出:
    // found multiple of 3: 3
    // found multiple of 3: 6
    // found multiple of 3: 9
}

Pull模式适用于无法使用range-over-func的场景,如需要在迭代过程中执行复杂控制流(跳过多个元素、条件性回溯等)。Pull模式产生的迭代函数需要在不再使用时调用stop()释放资源,通常通过defer保证。

性能考量与goroutine泄漏防护

range-over-func的Push模式在单线程内同步执行yield回调,不涉及goroutine创建,性能与手动for循环基本持平。Pull模式通过iter.Pull转换时,内部使用goroutine和channel实现,存在goroutine生命周期管理开销:

// Pull模式内部实现简化示意
func Pull[V any](pushSeq func(yield func(V) bool)) (func() (V, bool), func()) {
    ch := make(chan V)
    done := make(chan struct{})

    go func() {
        pushSeq(func(v V) bool {
            select {
            case ch <- v:
                return true
            case <-done:
                return false
            }
        })
        close(ch)
    }()

    next := func() (V, bool) {
        v, ok := <-ch
        return v, ok
    }

    stop := func() {
        close(done)
    }

    return next, stop
}

如果Pull模式中忘记调用stop(),且迭代器内部有未完成的yield调用,goroutine将阻塞在channel发送上导致泄漏。务必通过defer stop()或在明确终止路径上调用stop()。Push模式不存在此问题,应优先使用range-over-func的Push模式。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-yu-yan-fan-xing-die-dai-qi-she-ji-rangeoverfunc-ji-zhi/

(0)
小编小编
上一篇 16小时前
下一篇 16小时前

相关推荐