当前位置:首页 > 文章列表 > Golang > Go教程 > Go iter.Seq 怎样组合过滤迭代器

Go iter.Seq 怎样组合过滤迭代器

来源:17golang原创 2026-10-14 19:45:02 0浏览 收藏

我第一次把一段“遍历切片、判断条件、追加结果”的代码改成 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 把它传回产生输入的那一层。

Go iter.Seq 从源序列经过 Filter 谓词到下游 range 的结构说明图
图1:iter.Seq 过滤器把上游值交给谓词判断,再把保留值交给下游;这是静态结构说明图,不是运行截图。

叠加条件并收集结果

因为 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
}

不要把过滤器写成“无论下游要不要,都继续把全部值推完”的版本。那样在文件行、分页结果或带清理动作的生成器上,会把本来可以提前结束的工作继续做完。

Go iter.Seq 下游 yield 返回 false 向上游传播提前停止的结构说明图
图2:下游不再需要值时,false 沿 yield 边界让过滤适配器返回;这是静态关系说明图,不是运行截图。

如果过滤器包裹的上游还持有文件、锁或其他需要释放的资源,资源的生命周期应由上游迭代器自己管理,并在收到停止后完成清理。过滤器的职责是准确转发停止信号,不要在中间层偷偷吞掉这个边界。

迁移后的回归检查

把已有循环改为 iter.Seq 后,我会按下面的顺序做一轮小检查。它们不是额外的框架要求,而是用来确认迁移没有改变原来的结果和停止语义。

  • 编译环境至少是 Go 1.23;旧工具链不能解析 range-over-function 的写法。
  • 谓词返回假时只跳过当前值,不能把它误写成终止整个序列。
  • 每次调用下游 yield 都检查布尔返回值,并在假值分支立刻 return。
  • 空输入、全部被过滤和第一个值就命中的情况都要有明确结果。
  • 只有调用方确实需要随机访问或重复遍历时,才把序列收集成切片。
场景期望行为
输入为空过滤器不调用谓词,消费者直接结束
没有值通过返回空序列,收集结果为空切片
消费者提前返回过滤器停止调用下游,上游不再请求后续值
多个 Filter 嵌套每层只处理自己收到的值,并保留 false 传播

什么时候保留普通循环

iter.Seq 适合把同一套筛选逻辑应用到不同来源,或者让调用方决定“遍历到哪里”。但如果逻辑只有几行、只使用一次,而且结果本来就必须是切片,普通循环通常更直观。迁移的收益来自复用和惰性,不是把所有循环都包成函数。

我的判断是:先把谓词和停止边界写正确,再决定是否抽成通用适配器。只要记住 yield(false) 是“下游不再需要”的信号,Filter 就能自然地叠加,也不会在找到结果后继续消耗上游。

常见追问

iter.Seq 和切片有什么区别?切片是已经物化的数据,支持索引;iter.Seq 是按需产生值的函数约定,本身不提供索引。

过滤器是否必须使用泛型?为了让同一实现适配不同元素类型,泛型最自然;如果业务只有一种类型,也可以写具体类型版本。

什么时候应该调用 slices.Collect?当调用方需要保存结果、排序、索引或多次遍历时再收集;只需要顺序消费或找到首个结果时可以保持惰性。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go maps.Copy 怎样把配置键合并到目标映射Go maps.Copy 怎样把配置键合并到目标映射
上一篇
Go maps.Copy 怎样把配置键合并到目标映射
Go iter.Seq2 怎样遍历键值对迭代器
下一篇
Go iter.Seq2 怎样遍历键值对迭代器
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    473次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    531次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    551次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    503次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    319次使用