当前位置:首页 > 文章列表 > Golang > Go教程 > Go iter 怎样给 Seq2 设计两个值的 yield 约定

Go iter 怎样给 Seq2 设计两个值的 yield 约定

来源:17golang原创 2026-10-18 06:03:44 0浏览 收藏

为 Go iter.Seq2 设计 yield 约定,需要先给两个参数定义稳定含义,再把回调返回的 bool 单独解释为“是否继续”。例如 iter.Seq2[int, Job] 可以表示“原切片索引、任务值”;bool 不是第三个数据值,也不是默认的错误标志。每次调用 yield 提供一对值,收到 false 就停止迭代。

Seq2 常用于键值对或索引值对,但类型参数名 K、V 不强制第一项一定是 map 的键。API 文档还需要交代顺序、过滤后的索引、能否重复遍历,以及数据是副本还是共享引用。下面使用原创任务列表演示,代码未在本机编译运行。

官方地址:https://pkg.go.dev/iter

两个值是数据约定,bool 是继续信号

Seq2 的核心类型是 func(yield func(K, V) bool)。外层序列函数没有返回数据列表,而是把每一对数据交给回调。通常用 for k, v := range seq 消费时,编译器提供 yield;直接调用 seq 时,也可以由调用方自己提供回调。

位置本文约定不要混淆为
yield 的第一个参数sourceIndex,原切片索引自动产生的连续结果序号
yield 的第二个参数job,当前任务值默认附带错误语义的结果
yield 返回的 booltrue 继续,false 停止任务是否合法、操作是否成功

两个值也可以设计成“任务、错误”,但那是另一个需要明确说明终止方式的业务协议,不是 Seq2 自动赋予的规则。只有一个数据值时,优先用 Seq;存在三个互相关联字段时,通常把它们放进一个结构体,不必为了套用 Seq2 随意丢失字段。

明确原始索引和过滤后序号的区别

本文的 Job 只有 Name 与 Open 两个字段。OpenJobs 过滤 Open 为 false 的任务,第一个值仍取 sourceIndex:它定位的是输入切片中的位置。比如中间跳过关闭任务后,索引可以不连续。

如果消费者需要“第几个未关闭任务”,那应另设计数器并在文档中称为过滤后序号,不能让同一个索引参数一会儿表示来源位置,一会儿表示结果排名。只有名称 Seq2[int, Job] 还不足以让调用方猜出这个区别。

Job字段与OpenJobs双值约定的静态关系
图1:双值数据契约静态结构图。输入字段组说明过滤依据,输出约定组说明 sourceIndex 与 job 的来源;没有画迭代流程或运行结果。

实现可提前停止的 Seq2

下面完整示例需要 Go 1.23 及以上,项目语言版本也需允许 range-over-function。代码既展示 range 消费,也展示直接传回调;两种消费方法遵守同一停止约定。

package main

import (
    "fmt"
    "iter"
)

type Job struct {
    Name string
    Open bool
}

// OpenJobs按切片顺序提供未关闭任务;第一个值是原切片索引,不是过滤后序号。
// 此迭代器可重复遍历,但不创建快照,遍历期间调用方不得并发修改jobs。
func OpenJobs(jobs []Job) iter.Seq2[int, Job] {
    return func(yield func(int, Job) bool) {
        for sourceIndex, job := range jobs {
            if !job.Open {
                continue
            }
            // false表示消费者不再需要后续元素,应退出整个迭代器。
            if !yield(sourceIndex, job) {
                return
            }
        }
    }
}

func main() {
    jobs := []Job{
        {Name: "准备提纲", Open: true},
        {Name: "归档旧稿", Open: false},
        {Name: "整理插图", Open: true},
        {Name: "补充说明", Open: true},
    }
    seq := OpenJobs(jobs)
    for sourceIndex, job := range seq {
        fmt.Println(sourceIndex, job.Name)
        if sourceIndex == 2 {
            break // 编译器提供的yield将返回false,迭代器负责停止。
        }
    }
    // 可直接调用序列函数;这里仅演示消费一个元素后主动停止。
    seq(func(sourceIndex int, job Job) bool {
        fmt.Println("仅取一项:", sourceIndex, job.Name)
        return false
    })
}

按示例逻辑,第一次遍历应取得原索引 0 与 2 对应的任务,在索引 2 处 break,不再取得后面的“补充说明”。第二次直接调用 seq,应重新从输入开头取得一个未关闭任务。这是预期语义说明,并不是本文的运行日志。

if !yield(...) { return } 不能省略。消费者 break 后,编译器合成的 yield 返回 false,继续调用它违反 range 协议。嵌套循环实现中也应退出整个迭代器,而不是只 break 最里层循环后,又在外层继续提供值。

seq、yield与两种消费者的静态调用约定
图2:消费与停止约定静态说明图。序列提供组与消费约定组用连线表达调用和停止责任;连线无先后含义,也不是时间轴。

旧版本使用回调,不冒充相同语法

iter 包与对迭代器函数使用 range 的能力由 Go 1.23 提供。更早的工具链不能通过简单删除 import 就获得这种循环语法。如果暂时不能升级,可以公开传统的回调函数,保留双值和 bool 语义。

// 旧版本可采用显式回调;这里Job沿用上文声明,不使用iter包或range函数语法。
func WalkOpenJobs(jobs []Job, visit func(int, Job) bool) {
    for sourceIndex, job := range jobs {
        if job.Open && !visit(sourceIndex, job) {
            return // 同样遵守false停止的契约。
        }
    }
}

这是回调接口片段,Job 沿用前面的类型。调用方显式调用 WalkOpenJobs 并传入 visit,它不是旧版本里的 iter.Seq2,也不能直接写成 range WalkOpenJobs 的形式。保持业务契约一致,比假装语法兼容更清楚。

记录重入、所有权和资源边界

OpenJobs 的闭包捕获了切片描述符,没有深复制输入。每次调用会重新遍历捕获的切片范围,但元素内容仍可能受到外部修改影响,不能声称它是创建时的快照,也不要在遍历期间并发修改共享数据。Job 在本例中按值传递;若以后加入切片、map 或指针字段,值复制也不等于深复制。

不要保存编译器提供的 yield,等外层迭代函数返回后再从 goroutine 调用。序列函数应在自己的调用范围内提供元素,消费者提前停止后完成必要清理再返回。来自不可回退数据流的一次性序列,应专门写明可否继续、可否重遍历,不能照搬本例的行为。

如果迭代器内部获得了文件等资源,应明确由谁关闭,以及提前停止时如何清理;仅换成 Seq2 不会自动解决这些问题。本文示例没有外部资源,不虚构资源释放效果,也不声称函数迭代器必然比切片或 channel 更快。

第一个值一定必须能作 map 的键吗?

不是。Seq2 的两个类型参数约束都是 any;是否能作为 map 键,是消费端用途的额外约束。

不用第一个值时怎么写?

在循环变量位置用下划线忽略,例如 for _, job := range seq。只声明一个变量会接收第一个值,不能假定它自动变成第二个值。

yield 返回 false 后还能清理吗?

可以完成迭代器自身的必要清理并返回;不能再调用 yield 提供后续元素。停止提供值与释放自己拥有的资源并不矛盾。

参考文档:https://pkg.go.dev/iter、https://go.dev/ref/spec#For_range、https://go.dev/doc/go1.23。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go maps 怎样用 Maps.Copy 合并配置映射Go maps 怎样用 Maps.Copy 合并配置映射
上一篇
Go maps 怎样用 Maps.Copy 合并配置映射
Go maps 怎样判断 map 是否包含指定键
下一篇
Go maps 怎样判断 map 是否包含指定键
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    514次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    572次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    598次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    550次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    368次使用