当前位置:首页 > 文章列表 > Golang > Go教程 > Go iter.Seq 怎样设计可组合的惰性遍历

Go iter.Seq 怎样设计可组合的惰性遍历

来源:17golang原创 2026-10-20 05:07:45 0浏览 收藏

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 返回类型就自动成立。

生成器和组合器的参数依赖结构
图1:原创静态结构图。生成边界持有 names,组合边界持有筛选与转换规则;线条标明函数参数依赖,不是执行流程图。

提前停止是每一层的共同约定

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 不应为“把源跑完”继续调用回调。

各层迭代器共享的停止契约
图2:原创静态契约图。分组区分消费侧与迭代器侧,各层都受 yield 返回值约束;不展示时间线、运行轨迹或测量结果。

复用与物化要在接口上说明

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,方便别的生成器接入。

惰性是不是自动异步执行?

不是。本例没有创建后台任务;处理发生在消费者调用迭代器期间。慢回调仍会让本次消费变慢。

回调可以修改外部计数吗?

可以写出这样的代码,但我更愿意让转换与谓词尽量保持纯粹,否则一次早停与一次完整收集可能造成不同的外部状态。提交接口前应写明源是否重入、是否共享底层数据、回调是否有副作用,以及停止后还会不会继续工作。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go strconv.ParseBool 怎样兼容配置里的 yes/no 字符串Go strconv.ParseBool 怎样兼容配置里的 yes/no 字符串
上一篇
Go strconv.ParseBool 怎样兼容配置里的 yes/no 字符串
Go iter.Pull 怎样把推送式遍历改成拉取式
下一篇
Go iter.Pull 怎样把推送式遍历改成拉取式
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    556次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    606次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    632次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    588次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    413次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码