iter

Go 1.22+的 iter 包提供了一种强大的迭代机制,允许我们以声明式的方式处理序列。通过组合不同的序列操作(如过滤、映射),我们可以构建出复杂的数据处理管道,同时保持代码的清晰和高效。这种迭代方式特别适合处理流式数据或大型数据集。

1 迭代器说明

1.1 迭代器本质

迭代器是​​序列元素的生成函数​​,通过 yield 回调传递值:

type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)

1.2 控制流机制

  • ​继续迭代​​:yield 返回 true

  • ​提前终止​​:yield 返回 false

  • ​自然结束​​:序列元素耗尽

1.3 安全变异模式

问题​​:迭代器本身不直接支持修改

​解决方案​​:位置迭代器模式

// 1. 定义位置类型
type Pos[V any] struct { 
    node *TreeNode[V] 
}

// 2. 实现变异方法
func (p *Pos[V]) Delete() { 
    removeNode(p.node) 
}

// 3. 提供位置迭代器
func (t *Tree[V]) Positions() iter.Seq[*Pos[V]] {
    return func(yield func(*Pos[V]) bool) {
        traverse(t.root, func(n *TreeNode[V]) bool {
            return yield(&Pos{node: n})
        })
    }
}

// 4. 使用
for p := range tree.Positions() {
    if p.Value() < threshold {
        p.Delete() // 安全变异
    }
}

1.4 总结

  1. ​抽象维度​​:

    • 统一集合访问接口

    • 分离生成与消费逻辑

    • 隐藏底层数据结构

  2. ​资源维度​​:

    • 惰性加载节省内存

    • 精确控制资源生命周期

    • 流式处理无限数据

  3. ​性能维度​​:

    • 零拷贝数据管道

    • 按需计算避免浪费

    • 并发友好设计

  4. ​扩展维度​​:

    • 可组合的函数管道

    • 领域特定语言基础

    • 跨数据源统一访问

2 序列

2.1 Seq[T] 类型

  • ​本质​​:接收 yield 函数的函数

  • ​行为​​:

    • 每次调用 yield(value) 产生一个值

    • yield 返回 false,停止生成

  • ​特点​​:

    • 惰性求值(按需生成)

    • 可中断迭代

    • 无状态复用

    • ​声明式迭代​​:描述"生成什么"而非"如何生成"

    • ​消费者驱动​​:消费者通过 yield 控制流程

    • ​提前终止​​:当 yield 返回 false 时停止

  • 生命周期

2.2 Seq2[T1, T2] 类型

  • 用于生成键值对等二元组序列

  • 典型应用:映射遍历、分页数据

  • 二元组迭代​​:同时产生键和值

  • ​天然映射​​:适合键值对数据结构

  • ​高效遍历​​:避免中间数据结构

  • ​模式匹配​​:for k, v := range 语法糖

2.3 总结

SeqSeq2 类型为 Go 带来了现代化的迭代器模式,具有以下优势:

  1. ​声明式语法​​:代码更简洁易读

  2. ​资源安全​​:与 defer 完美结合

  3. ​无限序列​​:支持无限序列的处理

  4. ​组合能力​​:轻松组合多个操作

  5. ​并发友好​​:天然支持并发处理

3 工具函数

3.1 Pull:手动控制迭代

  • **seq Seq[V]:任意类型的推送式迭代器序列

  • ​返回值​​:

    • next func() (V, bool):获取下一个值

      • 返回值:(value, true) 有效值

      • 结束信号:(zero_value, false)

    • stop func():终止迭代,不会进行任何清理行为

关键特性

  1. ​惰性控制​​:按需拉取值,不预加载

  2. ​安全终止​​:stop 确保资源释放

  3. ​错误传播​​:panic 自动传递

  4. ​线程安全​​:禁止并发调用

3.2 Pull2:二元组版本

这两个函数用于将“推送式”迭代器(Seq 或 Seq2)转换为“拉取式”迭代器,通过返回的 nextstop 函数来控制迭代。

  • seq Seq2[K, V]:键值对类型的推送式迭代器

  • ​返回值​​:

    • next func() (K, V, bool):获取下一个键值对

      • 返回值:(key, value, true) 有效值

      • 结束信号:(zero_k, zero_v, false)

    • stop func():终止迭代

3.2.1 数据库分页查询

3.3 资源清理

可以把资源清理动作交给调用者,使迭代器代码专注于生成序列。

也可以使用自动绑定的方式

3.4 Next函数行为

在单次使用迭代器中,迭代器函数通常绑定到一个不可重放的流式数据源(如网络连接)。一旦迭代器函数返回(即序列结束),即使后续数据源又有新数据产生,迭代器也不会再产生新值。

但是,问题描述的场景是:在调用next函数返回(zero, false)(表示序列结束)之后,如果网络连接又传输了新数据,那么再次调用next会返回新数据吗?

答案:不会。原因如下:

  1. ​单次使用迭代器的定义​​:单次使用迭代器设计用于遍历一个序列一次。一旦序列结束(即迭代器函数返回),迭代器就处于结束状态,后续调用next只会返回(zero, false)

  2. ​迭代器函数的行为​​:迭代器函数内部通常是一个循环,该循环在数据源可用时产生值,并在数据源结束或外部通过yield返回false中断时退出。一旦这个循环退出,迭代器函数就返回了,不会再次进入循环。

  3. ​网络连接的特殊性​​:如果网络连接是一个持续产生数据的流(例如TCP连接),那么迭代器函数通常不会主动结束,除非遇到以下情况:

    • 网络连接关闭(读到EOF)

    • 读取发生错误

    • 外部通过yield返回false要求停止

3.5 yield函数行为

yield 是迭代器实现中的核心机制,它控制着值传递、流程中断和迭代终止。

​结束类型​

​触发条件​

​后续行为​

​自然结束​

迭代器函数执行完成

所有资源释放

​提前终止​

yield 返回 false

立即终止并清理

​错误终止​

内部发生 panic

传播 panic 并终止

迭代器函数结束后不会响应新值原因

  1. ​单次执行​​:迭代器函数只执行一次

  2. ​状态丢失​​:迭代位置和临时状态不保存

  3. ​资源释放​​:清理操作在结束时执行

  4. ​设计哲学​​:迭代器是值序列的快照

3.5.1 持久连接模式

3.5.2 事件驱动模式

3.5.3 状态恢复模式

3.6 设计选择指南

场景​

​推荐方案​

​新值响应​

静态数据

标准迭代器

不可能

实时数据流

长连接迭代器

自动响应

大文件处理

状态保存迭代器

手动恢复后响应

日志监控

事件驱动迭代器

自动响应

3.7 总结

  1. ​控制反转​​:

    • 消费者决定何时获取值

    • 精确控制处理节奏

    • 支持条件中断

  2. ​资源安全​​:

    • defer stop() 保证释放

    • 自动处理迭代器 panic

    • 零值安全处理

  3. ​模式转换​​:

    • 无缝衔接 push/pull 模式

    • 支持传统循环控制

    • 兼容复杂处理逻辑

  4. ​应用场景​​:

    • 大数据集分块处理

    • 条件搜索与过滤

    • 资源敏感型操作

    • 流式处理中断

最后更新于