Go iter.Seq 怎样设计可组合的惰性遍历
Go iter.Seq 的惰性组合,关键不在把所有循环改成函数,而是统一用 iter.Seq[T] 连接生成器与组合器:构造时只保存输入和规则,消费时才逐项处理;任何一层收到 yield 返回 false,都必须停止。这样才能在筛选、转换之后仍保留提前退出的能力。
Go 官方地址:https://go.dev/;iter 文档:https://pkg.go.dev/iter。下文采用 Go 1.23 及以上的迭代器语法,项目工具链和语言版本需相应支持,参见 Go 1.23 发布说明。
我会先判断是否真的需要惰性组合
假设要从名称列表里过滤空值、转为大写,但调用方可能只要第一个匹配项。对我来说,这类需求值得把“怎么产生元素”和“取多少元素”分开:调用方掌握退出位置,筛选和转换只承担单项规则。若只是一个很短、只用一次的循环,我仍倾向直接写 for,不为抽象而增加三个函数。
下面是为说明取舍设计的独立场景,不是生产压测或亲历故障记录。这里的预算是阅读成本、额外存储和接口复杂度,而不是云资源价格;也不需要引入 goroutine、channel 或后台任务。
| 方案 | 我会选择它的条件 | 代价 |
|---|---|---|
| 直接循环 | 规则局部且只使用一次 | 复用时可能重复业务判断 |
| 中间切片 | 需要索引、长度或多次访问结果 | 保存每一层结果 |
| Seq 组合 | 多个消费者共享变换规则,且允许早停 | 必须说明停止与重复遍历语义 |
把生成、筛选与转换留在闭包内
我会将函数划成 FromSlice、Where、Select 三个边界。FromSlice 接收 names 切片;Where 持有 keep 谓词;Select 持有 convert 转换函数。三个函数返回的都是函数值,调用方得到 labels 后才开始 range。以下完整代码为原创示例,未作为实测记录执行。
package main
import (
"fmt"
"iter"
"strings"
)
// FromSlice 只捕获切片;遍历开始后才逐项交付,不复制底层数组。
func FromSlice[T any](items []T) iter.Seq[T] {
return func(yield func(T) bool) {
for _, item := range items {
if !yield(item) {
return // 消费者不再接收时,立即结束这一层。
}
}
}
}
// Where 仅为满足条件的元素调用下游,不创建中间结果切片。
func Where[T any](src iter.Seq[T], keep func(T) bool) iter.Seq[T] {
return func(yield func(T) bool) {
for item := range src {
if keep(item) && !yield(item) {
return // 退出上游 range,使停止意愿继续传递。
}
}
}
}
// Select 将单项转换延迟到消费时;转换函数应尽量没有副作用。
func Select[A, B any](src iter.Seq[A], convert func(A) B) iter.Seq[B] {
return func(yield func(B) bool) {
for item := range src {
if !yield(convert(item)) {
return
}
}
}
}
func main() {
names := []string{"spruce", "", "moss", "fern"}
chosen := Where(FromSlice(names), func(s string) bool {
return s != "" // 忽略空名称,不把筛选提前执行。
})
labels := Select(chosen, strings.ToUpper)
// 只取首个匹配项;构造 labels 本身不会调用转换函数。
for label := range labels {
fmt.Println(label)
break
}
}
按这个示例的规则,首个交付值应为 SPRUCE;空名称会被过滤,break 后不再向后转换 moss 与 fern。构造 chosen 与 labels 时不预遍历源,也不先生成一份过滤切片。这个“懒”来自函数实现位置,不是给任意函数加上 Seq 返回类型就自动成立。

提前停止是每一层的共同约定
iter.Seq 是接收 yield 回调的函数类型,yield 的布尔值表示是否继续。官方接口约定见 iter 文档。对 range 函数的语言规则,见 Go 规范的 range 条款:消费者退出循环后,迭代器不能继续调用已返回 false 的 yield。
在这份实现中,Select 与 Where 的 return 会结束各自的上游 range,FromSlice 则直接检查 yield。我的取舍是让所有组合器使用同一种退出写法,而不是某层用 break 跳出内部小循环后又继续交付。停止不是异常,也不需要补发零值或关闭一个并不存在的 channel。
消费者的 break、Select、Where、FromSlice 与 yield 返回值构成同一个停止契约。Select 不得吞掉 false,Where 不能因为还有未检查的元素而继续,FromSlice 不应为“把源跑完”继续调用回调。

复用与物化要在接口上说明
FromSlice 的循环变量位于返回的函数内部,因此对同一函数值再次遍历,会从切片开头重新走。但它只复制了切片描述信息,并没有冻结底层元素;我不会把它命名成 Snapshot。调用方若改了底层数组,后续遍历可能读到变化。Where 与 Select 是否可重复消费,也依赖它们的源以及回调自身的状态。
如果要保存转换结果,可以显式收集。下面片段放在 main 的 labels 构造之后,另需导入标准库 slices;它表示完整消费,而不是继续上一次 break 的位置。
// 物化所有结果:调用方开始承担结果切片的存储。 all := slices.Collect(labels) // 对本例可重入源再次遍历;不会自动缓存前一次转换结果。 again := slices.Collect(labels) _, _ = all, again
收集接口的定义见 slices.Collect 文档。我会把收集留在业务边缘:需要索引和保存的调用方才收集,只要逐项处理的调用方继续使用 range。若源是不能回退的流,就必须明确声明一次性语义,不把本例的重入能力推广给所有 Seq。
适合拆成 Seq 的场景与取舍
适合的是规则需要重组、调用方可能早停、元素可逐项独立处理的任务。不适合的是调用方始终要随机访问所有结果,或简单循环比组合关系更容易读懂的地方。本文没有性能数据,不声称 Seq 一定更快或零分配;不建中间切片也不等于没有闭包成本。
Where 和 Select 是标准库自带的吗?
不是,它们是本文定义的通用函数。名称表达角色,接口保持标准 iter.Seq,方便别的生成器接入。
惰性是不是自动异步执行?
不是。本例没有创建后台任务;处理发生在消费者调用迭代器期间。慢回调仍会让本次消费变慢。
回调可以修改外部计数吗?
可以写出这样的代码,但我更愿意让转换与谓词尽量保持纯粹,否则一次早停与一次完整收集可能造成不同的外部状态。提交接口前应写明源是否重入、是否共享底层数据、回调是否有副作用,以及停止后还会不会继续工作。
Go strconv.ParseBool 怎样兼容配置里的 yes/no 字符串
- 上一篇
- Go strconv.ParseBool 怎样兼容配置里的 yes/no 字符串
- 下一篇
- Go iter.Pull 怎样把推送式遍历改成拉取式
-
- Golang · Go教程 | 28分钟前 | 标准库 · go · url · Go net/url Userinfo url.UserPassword
- Go url.UserPassword 怎样构造带认证信息的 URL
- 160浏览 收藏
-
- Golang · Go教程 | 29分钟前 | Go io.ReadAll io.LimitReader 受限读取
- Go io.ReadAll 怎样配合 LimitReader 防止无界读取
- 176浏览 收藏
-
- Golang · Go教程 | 30分钟前 | 标准库 · 缓存 · HTTP · Go教程 · Go http.ServeContent Last-Modified If-Modified-Since ModTime
- Go http.ServeContent 怎样利用修改时间处理缓存
- 323浏览 收藏
-
- Golang · Go教程 | 1小时前 | 标准库 · JSON · go · Go token encoding/json/jsontext JSON token流
- Go encoding/json/jsontext 怎样读取 JSON token
- 271浏览 收藏
-
- Golang · Go教程 | 1小时前 | Go crypto/mlkem ML-KEM-768 后量子密钥协商
- Go crypto/mlkem 怎样封装后量子密钥协商
- 406浏览 收藏
-
- Golang · Go教程 | 1小时前 | 内存管理 · Go教程 · Go 垃圾回收 资源释放 runtime.AddCleanup
- Go runtime.AddCleanup 怎样注册对象清理回调
- 304浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · 泛型 · 内存优化 · Go Handle unique.Make 值唯一化
- Go unique.Make 怎样复用可比较值的唯一实例
- 329浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · 垃圾回收 ·
- Go weak.Pointer 怎样保存可回收对象的弱引用
- 175浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 556次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 606次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 632次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 588次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 413次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览

