Go iter.Seq 怎样组合过滤迭代器
我第一次把一段“遍历切片、判断条件、追加结果”的代码改成 iter.Seq,最容易写错的地方不是泛型,而是 yield 的返回值:下游返回 false 时,过滤器必须立刻返回,不能继续向上游取值。这样封装出来的过滤器才既能组合,又能保留惰性和提前停止。
官方文档:https://pkg.go.dev/iter
下面的示例以 Go 1.23 及更高版本为范围。Go 1.23 让 for range 可以遍历符合约定的函数类型,并新增 iter.Seq、slices.Collect 等配套能力。本文只围绕 Seq[V] 的过滤和停止传播展开,不把它扩展成并发流水线。
从旧循环迁到 iter.Seq
旧写法通常把筛选逻辑和消费逻辑揉在一起:循环拿到一个值,判断后追加到结果切片。它当然没有问题,但当输入从切片换成映射值、文件行或另一个生成器时,过滤代码很容易复制多份。
func LongNames(names []string) []string {
result := make([]string, 0, len(names))
for _, name := range names {
// 只保留长度达到条件的名称。
if len(name) >= 6 {
result = append(result, name)
}
}
return result
}
迁移后的思路是把“值从哪里来”和“值是否保留”拆开。输入统一为 iter.Seq[V],过滤器只负责调用谓词和转发 yield,最后是否收集成切片由调用方决定。
| 关注点 | 旧循环 | iter.Seq 组合方式 |
|---|---|---|
| 输入形态 | 通常绑定切片或某一种容器 | 只要求实现 iter.Seq[V] |
| 过滤位置 | 写在消费循环内部 | 独立成可复用适配器 |
| 结果分配 | 循环时通常立即追加 | 需要时再用 slices.Collect |
| 停止语义 | 由循环里的 break 控制 | 由 yield 的 false 向上游传播 |
实现可组合的过滤器
过滤器的签名可以保持很小:接收一个上游序列和一个判断函数,返回另一个序列。真正的迭代发生在返回的闭包被消费时,所以创建过滤器本身不会遍历输入。
package main
import "iter"
// Filter 返回一个惰性过滤器,只把满足 keep 的值交给下游。
func Filter[V any](input iter.Seq[V], keep func(V) bool) iter.Seq[V] {
return func(yield func(V) bool) {
for value := range input {
// 谓词不通过时跳过当前值,但继续观察后续值。
if !keep(value) {
continue
}
// 下游返回 false 表示已经不需要更多值,必须停止转发。
if !yield(value) {
return
}
}
}
}
这里有两个边界值得记住。第一,keep 为假只代表当前值被过滤,不代表整个序列结束。第二,yield 为假才是下游的停止信号;过滤器要用 return 把它传回产生输入的那一层。

叠加条件并收集结果
因为 Filter 的输入和输出都是 iter.Seq[V],多个条件可以像套壳一样组合。下面先从切片得到序列,再叠加两个过滤条件,最后才把结果收集到新切片。
package main
import (
"fmt"
"slices"
)
func main() {
values := []int{3, 8, 12, 15, 21, 24}
positive := slices.Values(values)
// 第一层筛选偶数,第二层筛选大于 10 的值。
largeEven := Filter(
Filter(positive, func(value int) bool {
return value%2 == 0
}),
func(value int) bool {
return value > 10
},
)
// 只有需要物化结果时才分配并返回切片。
result := slices.Collect(largeEven)
fmt.Println(result)
}
两层过滤器不会因为嵌套就提前生成中间切片。消费端通过 range 取值时,外层过滤器向内层要值,内层再向 slices.Values 要值;每个值只沿着当前组合链路向前传递。若结果只需要找到第一个匹配项,就不必调用 slices.Collect。
让提前停止向上游传播
我觉得这是从普通循环迁移时最容易漏掉的回归点。下游一旦找到答案,yield 会返回 false。这个返回值会让过滤器的 yield(value) 返回假,于是过滤器执行 return,上游的 range input 也随之结束。
// FirstMatch 返回第一个满足条件的值,不会继续扫描剩余输入。
func FirstMatch[V any](input iter.Seq[V], keep func(V) bool) (V, bool) {
var found V
for value := range Filter(input, keep) {
// 找到目标后离开 range,编译器会让迭代器收到停止信号。
found = value
return found, true
}
return found, false
}
不要把过滤器写成“无论下游要不要,都继续把全部值推完”的版本。那样在文件行、分页结果或带清理动作的生成器上,会把本来可以提前结束的工作继续做完。

如果过滤器包裹的上游还持有文件、锁或其他需要释放的资源,资源的生命周期应由上游迭代器自己管理,并在收到停止后完成清理。过滤器的职责是准确转发停止信号,不要在中间层偷偷吞掉这个边界。
迁移后的回归检查
把已有循环改为 iter.Seq 后,我会按下面的顺序做一轮小检查。它们不是额外的框架要求,而是用来确认迁移没有改变原来的结果和停止语义。
- 编译环境至少是 Go 1.23;旧工具链不能解析 range-over-function 的写法。
- 谓词返回假时只跳过当前值,不能把它误写成终止整个序列。
- 每次调用下游
yield都检查布尔返回值,并在假值分支立刻return。 - 空输入、全部被过滤和第一个值就命中的情况都要有明确结果。
- 只有调用方确实需要随机访问或重复遍历时,才把序列收集成切片。
| 场景 | 期望行为 |
|---|---|
| 输入为空 | 过滤器不调用谓词,消费者直接结束 |
| 没有值通过 | 返回空序列,收集结果为空切片 |
| 消费者提前返回 | 过滤器停止调用下游,上游不再请求后续值 |
| 多个 Filter 嵌套 | 每层只处理自己收到的值,并保留 false 传播 |
什么时候保留普通循环
iter.Seq 适合把同一套筛选逻辑应用到不同来源,或者让调用方决定“遍历到哪里”。但如果逻辑只有几行、只使用一次,而且结果本来就必须是切片,普通循环通常更直观。迁移的收益来自复用和惰性,不是把所有循环都包成函数。
我的判断是:先把谓词和停止边界写正确,再决定是否抽成通用适配器。只要记住 yield(false) 是“下游不再需要”的信号,Filter 就能自然地叠加,也不会在找到结果后继续消耗上游。
常见追问
iter.Seq 和切片有什么区别?切片是已经物化的数据,支持索引;iter.Seq 是按需产生值的函数约定,本身不提供索引。
过滤器是否必须使用泛型?为了让同一实现适配不同元素类型,泛型最自然;如果业务只有一种类型,也可以写具体类型版本。
什么时候应该调用 slices.Collect?当调用方需要保存结果、排序、索引或多次遍历时再收集;只需要顺序消费或找到首个结果时可以保持惰性。
Go maps.Copy 怎样把配置键合并到目标映射
- 上一篇
- Go maps.Copy 怎样把配置键合并到目标映射
- 下一篇
- Go iter.Seq2 怎样遍历键值对迭代器
-
- Golang · Go教程 | 2分钟前 |
- Go text/template 怎样让缺失字段显式报错
- 468浏览 收藏
-
- Golang · Go教程 | 4分钟前 |
- Go html/template 怎样复用页面布局块
- 116浏览 收藏
-
- Golang · Go教程 | 8分钟前 |
- Go TLS 怎样声明 ALPN 协议列表
- 230浏览 收藏
-
- Golang · Go教程 | 10分钟前 | Go教程 · 签名校验 crypto/hmac ConstantTimeCompare 常量时间比较 Go HMAC
- Go HMAC 怎样用 ConstantTimeCompare 比较签名
- 464浏览 收藏
-
- Golang · Go教程 | 12分钟前 |
- Go crypto/rand 怎样生成安全范围内随机整数
- 468浏览 收藏
-
- Golang · Go教程 | 22分钟前 |
- Go slog 怎样把公共字段分组输出
- 189浏览 收藏
-
- Golang · Go教程 | 26分钟前 | 并发安全 · go · Go slog.Handler slog.Record Record.Clone
- Go slog.Record Clone 怎样隔离 Handler 的共享状态
- 381浏览 收藏
-
- Golang · Go教程 | 27分钟前 |
- Go 测试怎样只运行指定的基准子测试
- 220浏览 收藏
-
- Golang · Go教程 | 46分钟前 |
- Go 模糊测试失败后怎样保留回归语料
- 339浏览 收藏
-
- Golang · Go教程 | 48分钟前 | 测试 · go · testing.F.Add testdata/fuzz Go模糊测试 Go种子语料
- Go 模糊测试怎样组织种子语料
- 265浏览 收藏
-
- Golang · Go教程 | 49分钟前 |
- Go t.Run 怎样设计稳定的子测试名称
- 497浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 473次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 531次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 551次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 503次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 319次使用
-
- GScript 编写标准库示例详解
- 2022-12-30 369浏览
-
- 关于Golang标准库flag的全面讲解
- 2023-02-25 344浏览
-
- Golang标准库unsafe源码解读
- 2022-12-29 464浏览
-
- Go泛型实战教程之如何在结构体中使用泛型
- 2023-02-24 234浏览
-
- 关于go-zero单体服务使用泛型简化注册Handler路由的问题
- 2022-12-30 346浏览
