登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go 怎样用 iter.Seq 表达惰性序列

来源:17golang原创

时间:2026-10-17 17:43:23 393浏览 收藏

用 iter.Seq[T] 表达惰性序列,可以返回一个接收 yield func(T) bool 的函数,把每个值交给 yield;只要 yield 返回 false,就结束当前序列函数。调用方用 range 消费,不必先收集完整切片。

示例要求 Go 1.23 及以后版本,并将模块的语言版本设为至少 go 1.23。官方入口:https://pkg.go.dev/iter;版本说明:https://go.dev/doc/go1.23。本文只说明 Seq 的产出与停止契约,不展开 Pull、并发管道或错误通道设计。

只取前三个值,先把最小代码写对

package main

import (
    "fmt"
    "iter"
)

// Tickets 每次遍历从1重新开始;n

这里的用户任务是“编号范围很大,但本次只需预览前三个”。按代码语义,预期依次打印1、2、3,不会先分配装有百万个编号的切片。本文没有执行这段代码,打印值只是推导预期,不是运行记录或性能测试。

最重要的一行是 if !yield(i + 1) { return }。第三个值已经进入循环体,break 表达的是“不再接收后面的值”,而不是撤销第三个值。

把构造、产出和消费分成三个职责

Tickets(n) 是构造函数,捕获上限并返回序列函数;返回的 Seq 才是生产者,保存遍历规则而不是完整结果集合;range 循环体是消费者,规定一次拿到编号后怎样处理。yield 回调是两者之间的交接接口,布尔返回值是继续许可。

这种职责拆分让调用点更容易读懂:函数名表达遍历什么,参数表达边界,循环体表达消费任务。它不是可视界面,因而这里的“易用性”是接口可理解性,不靠增加按钮或界面层来解释。也不要把 Seq 类型名本身当成惰性保证:如果在构造函数里先生成全部结果,再包一层闭包,前置成本照样已经付出。

序列构造与消费接口的静态关系
图1:构造函数与序列函数属于生产侧,yield 和消费者属于接口侧;连线说明职责关联,不表示执行时间轴。

提前退出时,false 必须得到尊重

range 在循环体正常完成一次迭代时,让 yield 返回 true;遇到 break 等结束该循环的控制转移时,让 yield 返回 false。生产者不能忽略这个结果继续调用 yield,也不能把 yield 保存起来等序列函数返回后再调用。语言规范的对应入口:https://go.dev/ref/spec#For_range。

直接使用函数调用时,协议也相同,只是回调由调用方提供。下面的辅助函数把每个已经交付的值放进短切片,到第二个就返回 false;若序列自然结束得更早,则只得到实际交付的值。

// 直接调用同一个迭代器时,回调承担消费者角色。
func FirstTwo(seq iter.Seq[int]) []int {
    result := make([]int, 0, 2)
    seq(func(v int) bool {
        result = append(result, v)
        return len(result) 

这个片段与完整示例放在同一个包中使用,复用已导入的 iter。它假设传入的 Seq 遵守停止约定,不用额外逻辑纵容不合约的生产者。错误写法包括直接丢弃 yield 的返回值、false 后继续产生昂贵数据,以及在外部 goroutine 里异步保留回调。

把清理放在真正拥有资源的函数里

示例只做整数计算,没有资源需要关闭。如果扩展后的生产者在序列函数内部获取资源,应在该层安排 defer;接收到 false 后 return,才会沿正常返回路径执行该层的清理。不要在构造函数先打开资源却期待一个从未被遍历的 Seq 自动关闭它。

停止迭代不等于任意底层操作都能被中断。假设生产者在调用下一次 yield 前执行了长时间阻塞操作,消费者的 break 并不会跨越那个阻塞点替你取消操作。需要取消的资源必须另有合适机制,不能把 yield 的布尔值解释成全系统取消信号。

停止许可与生产者清理的静态边界
图2:停止许可约束序列函数的后续产出,defer 绑定资源所属函数;不是取消传播流程或实际运行截图。

重复遍历、缓存和性能需要单独约定

Tickets 的游标 i 定义在返回函数内部,所以同一个 seq 再次被遍历时,会重新从1开始;它没有缓存上一次结果。若把游标移到外层闭包环境,重入行为就可能不同。可重复遍历和只能消费一次的序列都能用 Seq 表示,接口说明必须说清楚,不能让调用方只看类型猜测。

惰性表达的直接价值是按消费需求安排工作,不是自动并行、自动缓存或自动更快。当前例子没有创建 goroutine;循环体没有返回前,生产者在这次 yield 里也不会继续准备下一个编号。若最终必须保留全部结果,收集这些结果的内存成本仍然存在。

判断是否适合替换切片返回值,可以关注两个业务事实:消费者是否经常只取少量前缀,以及生成剩余值是否有明显成本。没有测量时不写加速比例;小而固定的结果集保留切片接口也可能更清楚。

边界行为速查

情况生产者职责调用方看到什么
正常消费yield 为 true 时允许继续逐值处理
提前结束false 后立即停止产出已交付值保留,后续值不再交付
n 小于等于0直接自然返回零次循环
再次遍历本例重新建立局部游标从1开始,不是续读

拿到 Seq 就开始计算了吗? 本例没有;是否在构造时做工作取决于实现位置,不能单凭返回类型判断。

break 以后还能调用同一次的 yield 吗? 不可以。false 表示该次遍历的继续许可已撤销,生产者应 return,而不是等消费者再次改变主意。

空序列要返回 nil 吗? 不需要。本例 n 不为正时仍返回可调用函数,只是不调用 yield;直接把 nil 函数交给 range 并不等于一个安全的空序列。

实现 Seq 时,把“产出一个值”和“检查还能不能继续”放在一起,最容易避免过量工作;构造时机、资源归属和重复遍历语义则写进接口约定。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>