Go iter.Seq 怎样组合过滤迭代器
来源:17golang原创
时间:2026-10-14 19:45:02 268浏览 收藏
我第一次把一段“遍历切片、判断条件、追加结果”的代码改成 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?当调用方需要保存结果、排序、索引或多次遍历时再收集;只需要顺序消费或找到首个结果时可以保持惰性。
-
369 收藏
-
344 收藏
-
464 收藏
-
234 收藏
-
346 收藏
-
468 收藏
-
116 收藏
-
488 收藏
-
230 收藏
-
464 收藏
-
468 收藏
-
189 收藏
-
381 收藏
-
220 收藏
-
339 收藏
-
265 收藏
-
497 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习