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 类型名本身当成惰性保证:如果在构造函数里先生成全部结果,再包一层闭包,前置成本照样已经付出。

提前退出时,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 的布尔值解释成全系统取消信号。

重复遍历、缓存和性能需要单独约定
Tickets 的游标 i 定义在返回函数内部,所以同一个 seq 再次被遍历时,会重新从1开始;它没有缓存上一次结果。若把游标移到外层闭包环境,重入行为就可能不同。可重复遍历和只能消费一次的序列都能用 Seq 表示,接口说明必须说清楚,不能让调用方只看类型猜测。
惰性表达的直接价值是按消费需求安排工作,不是自动并行、自动缓存或自动更快。当前例子没有创建 goroutine;循环体没有返回前,生产者在这次 yield 里也不会继续准备下一个编号。若最终必须保留全部结果,收集这些结果的内存成本仍然存在。
判断是否适合替换切片返回值,可以关注两个业务事实:消费者是否经常只取少量前缀,以及生成剩余值是否有明显成本。没有测量时不写加速比例;小而固定的结果集保留切片接口也可能更清楚。
边界行为速查
| 情况 | 生产者职责 | 调用方看到什么 |
|---|---|---|
| 正常消费 | yield 为 true 时允许继续 | 逐值处理 |
| 提前结束 | false 后立即停止产出 | 已交付值保留,后续值不再交付 |
| n 小于等于0 | 直接自然返回 | 零次循环 |
| 再次遍历本例 | 重新建立局部游标 | 从1开始,不是续读 |
拿到 Seq 就开始计算了吗? 本例没有;是否在构造时做工作取决于实现位置,不能单凭返回类型判断。
break 以后还能调用同一次的 yield 吗? 不可以。false 表示该次遍历的继续许可已撤销,生产者应 return,而不是等消费者再次改变主意。
空序列要返回 nil 吗? 不需要。本例 n 不为正时仍返回可调用函数,只是不调用 yield;直接把 nil 函数交给 range 并不等于一个安全的空序列。
实现 Seq 时,把“产出一个值”和“检查还能不能继续”放在一起,最容易避免过量工作;构造时机、资源归属和重复遍历语义则写进接口约定。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
403 收藏
-
209 收藏
-
313 收藏
-
468 收藏
-
460 收藏
-
219 收藏
-
408 收藏
-
376 收藏
-
495 收藏
-
412 收藏
-
309 收藏
-
127 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习