lo 项目 it 包 RepeatBy 详解:基于迭代器惰性生成序列的 Go 泛型函数
2026/9/13 14:06:01 网站建设 项目流程

lo 项目 it 包 RepeatBy 详解:基于迭代器惰性生成序列的 Go 泛型函数

【免费下载链接】lo💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo

本篇技术指南聚焦 lo 项目it包(基于 Go 1.23+ 迭代器iter.Seq的集合操作库)中的RepeatBy函数,讲解如何通过回调函数按索引生成指定长度的惰性序列,并深入源码剖析其底层实现、提前终止语义,以及与切片版RepeatByRepeatTimes等相似辅助函数的关系。读完本文,你将掌握it.RepeatBy的完整用法、适用场景与底层工作原理,能够在自己的迭代器流水线中熟练使用。

一、函数概览与签名

it.RepeatBy位于it包中,其核心语义是:通过 N 次调用回调函数(callback)构建一个序列(sequence),每次调用时回调都会收到当前的下标index,并返回一个类型为T的值,这些返回值按顺序组成最终的序列。

根据 docs/data/it-repeatby.md 中的 frontmatter 签名信息,函数签名如下:

func RepeatByT any T) iter.Seq[T]

逐参数拆解:

参数类型说明
countint回调被调用的次数,也是生成序列的元素个数
callbackfunc(index int) T接收当前下标、返回元素值的回调函数
返回值iter.Seq[T]惰性序列,可用for range消费

值得注意的关键点:

  • 泛型约束为T any:这是与同包Repeat的重要区别。Repeat要求T lo.Clonable[T](因为要复制同一个初始值),而RepeatBy对元素类型没有任何约束,任意类型均可使用;
  • 回调携带下标:回调的入参是index int,因此生成的值可以依赖位置信息,例如index * 2fmt.Sprintf("item-%d", index+1)等,天然适合生成带序号的数据;
  • 返回类型为iter.Seq[T]:这是 Go 1.23 引入的标准迭代器类型,只有被for range消费时才会真正执行回调,属于惰性求值。

二、基础用法示例

文档 docs/data/it-repeatby.md 给出了两个完整示例,完整继承如下。

2.1 生成带序号的字符串序列

result := it.RepeatBy(3, func(index int) string { return fmt.Sprintf("item-%d", index+1) }) var output []string for item := range result { output = append(output, item) } // output contains ["item-1", "item-2", "item-3"]

回调收到的index依次为0、1、2,因此index+1恰好得到1、2、3,拼接后即为item-1、item-2、item-3

2.2 生成基于下标的整数序列

result2 := it.RepeatBy(5, func(index int) int { return index * 2 }) var output2 []int for item := range result2 { output2 = append(output2, item) } // output2 contains [0, 2, 4, 6, 8]

index * 2依次得到0、2、4、6、8,可以看到下标从 0 开始,这也与 Go 切片、数组的索引习惯保持一致。

2.3 借助slices.Collect简化消费

仓库中的可运行示例 it/seq_example_test.go 展示了更简洁的收集写法:

func ExampleRepeatBy() { result := RepeatBy(5, func(i int) string { return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10) }) fmt.Printf("%v", slices.Collect(result)) // Output: [0 1 4 9 16] }

这里使用了 Go 标准库slices.Collectiter.Seq[T]一次性收集为切片,配合平方计算,输出[0 1 4 9 16],与文档第 2.2 节示例相互印证。

三、源码级剖析:惰性求值与提前终止

3.1 底层实现

it.RepeatBy的完整实现在 it/seq.go:

// RepeatBy builds a sequence with values returned by N calls of transform. // Play: https://go.dev/play/p/i7BuZQBcUzZ func RepeatByT any T) iter.Seq[T] { return func(yield func(T) bool) { for i := range count { if !yield(callback(i)) { return } } } }

从源码结构可以看出三个关键实现事实:

  1. 返回的是闭包函数RepeatBy并不立即计算任何值,而是返回一个类型为func(yield func(T) bool)的函数,这正是iter.Seq[T]的标准形态;
  2. for i := range count驱动循环:循环次数由count决定,i依次取0count-1,直接作为回调参数;
  3. 尊重 yield 的布尔返回值:每次调用yield(callback(i))后检查返回值,若为false立即return,实现提前终止(early break)。

需要说明的是,it包整体基于 Go 1.23+ 的迭代器机制,it/seq.go 文件头部带有//go:build go1.23构建标签,因此使用本函数的前提是 Go 版本不低于 1.23。

3.2 提前终止语义的测试验证

RepeatBy返回的序列支持消费者中途break,这一点由 it/seq_test.go 中的TestRepeatBy显式验证:

func TestRepeatBy(t *testing.T) { t.Parallel() cb := func(i int) int { return int(math.Pow(float64(i), 2)) } tests := []struct { name string count int expected []int }{ {name: "zero", count: 0, expected: nil}, {name: "two", count: 2, expected: []int{0, 1}}, {name: "five", count: 5, expected: []int{0, 1, 4, 9, 16}}, } for _, tt := range tests { tt := tt //nolint:modernize t.Run(tt.name, func(t *testing.T) { t.Parallel() is := assert.New(t) seq := RepeatBy(tt.count, cb) assertSeqSupportBreak(t, seq) is.Equal(tt.expected, slices.Collect(seq)) }) } }

测试覆盖了几个重要行为:

  • count = 0时返回空序列slices.Collect结果为nil,不会调用任何一次回调;
  • 结果与下标一一对应:平方回调下,count = 5得到[0, 1, 4, 9, 16],即
  • 支持 break/return:测试中的assertSeqSupportBreak(定义于 it/lo_test.go)会先以break中途退出遍历,再以return中途退出遍历,验证两者都不会 panic,从而保证序列是安全的惰性迭代器。

3.3 惰性求值带来的实战收益

由于RepeatBy返回的是惰性序列,回调只在消费时执行,因此可以:

  • 按需取前几个元素:例如count传 10000,但消费者只range前 3 个就break,回调实际只被调用 3 次,避免无谓计算;
  • 串联下游算子it包中MapFilterTake等函数都接受iter.Seq或泛型迭代器接口,RepeatBy的返回值可以直接作为它们的输入,构成流水线;
  • 与其他it序列函数组合:如it.Take(it.RepeatBy(...), n)取前 n 项等模式。

四、与相似辅助函数的对比

文档 frontmatter 的similarHelpers字段列出了三个相近函数,结合源码对比有助于准确选型。

4.1it.Repeat:重复同一个克隆值

it/seq.go 中的Repeat本质上是RepeatBy的特例:

func Repeat[T lo.Clonable[T]](count int, initial T) iter.Seq[T] { return RepeatBy(count, func(int) T { return initial.Clone() }) }

它把RepeatBy的回调固定为“忽略下标、返回initial.Clone()”,因此:

  • 生成的是 N 个相同值的序列;
  • 类型约束更严,要求T实现lo.Clonable接口(见 types.go 中Clonable的定义),每次调用Clone()保证元素相互独立;
  • 若需要每个元素都是独立的副本,优先用Repeat;若元素依赖下标或需要差异化计算,用RepeatBy

4.2 切片版RepeatBy:一次性急切求值

lo 核心包(非it包)同样提供RepeatBy,实现于 slice.go:

func RepeatByT any T) []T { result := make([]T, count) for i := 0; i < count; i++ { result[i] = callback(i) } return result }

两者的核心差异在于求值时机与返回类型:

维度lo.RepeatBy(核心包)it.RepeatBy(it 包)
返回类型[]T(切片)iter.Seq[T](迭代器)
求值时机立即调用 count 次回调,预分配切片惰性,消费时才调用回调
内存占用一次性分配count容量无固定分配,随消费流动
提前终止不支持(结果已完整生成)支持 break/return

文档示例 docs/data/core-repeatby.md 可查看核心包切片版的详细说明。选择原则:需要完整切片结果、元素会被反复遍历时用核心包版本;构建流水线、追求惰性与按需计算时用it包版本。

4.3TimesRepeat的姊妹关系

文档similarHelpers中还列出了core#slice#timesiter#sequence#times。slice.go 中核心包TimesRepeatBy形制相似(同样是count + func(index int) T的签名),但语义上更偏向"执行 N 次"的副作用场景;而it包序列版Times的文档见 docs/data/it-times.md。选型时主要看:需要的是带下标的差异化生成RepeatBy/Times),还是无差别的同值复制Repeat)。

五、典型实战场景与注意事项

5.1 生成测试数据

RepeatBy最常用的场景之一是批量构造测试输入:

ids := it.RepeatBy(100, func(index int) int64 { return int64(1000 + index) }) // 生成 1000..1099 的 ID 序列

配合slices.Collect即可转成切片供测试断言使用。

5.2 生成带序号的对象序列

利用index参数可以生成结构体序列,例如按顺序编号的订单、座位号、行号等:

type Row struct { No int Name string } rows := it.RepeatBy(10, func(index int) Row { return Row{No: index + 1, Name: fmt.Sprintf("row-%d", index+1)} })

5.3 注意事项

  • count为负数或 0 的行为for i := range countcount <= 0时不会进入循环体,序列为空,回调不会被调用(测试用例 "zero" 即验证了count = 0返回nil);
  • 回调的副作用时机:由于惰性求值,回调可能只在消费者遍历到对应位置时才执行,若回调存在副作用(如写日志、累加计数),需要理解其执行时机是"消费时"而非"调用RepeatBy时";
  • Go 版本要求it包依赖 Go 1.23+ 的iter标准包,使用前请确认go.mod中的 Go 版本(当前仓库根目录 go.mod 可查看项目的最低版本约束)。

六、小结

it.RepeatBy是 lo 项目it包中生成"按下标规则序列"的标准工具:它以count + func(index int) T的简洁签名,返回惰性的iter.Seq[T],底层通过闭包与yield机制支持提前终止,与核心包切片版RepeatBy互为"惰性 / 急切"两种形态,并与RepeatTimes组成完整的序列生成家族。在需要生成测试数据、带序号的对象集合或作为迭代器流水线起点时,它都是高效且类型安全的选择。

延伸阅读

  • 核心包切片版RepeatBy文档:docs/data/core-repeatby.md
  • 同值复制序列Repeat文档:docs/data/core-repeat.md
  • Times序列版本文档:docs/data/it-times.md
  • 迭代器序列模块总览:docs/docs/iter/sequence.md

【免费下载链接】lo💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询