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函数,讲解如何通过回调函数按索引生成指定长度的惰性序列,并深入源码剖析其底层实现、提前终止语义,以及与切片版RepeatBy、Repeat、Times等相似辅助函数的关系。读完本文,你将掌握it.RepeatBy的完整用法、适用场景与底层工作原理,能够在自己的迭代器流水线中熟练使用。
一、函数概览与签名
it.RepeatBy位于it包中,其核心语义是:通过 N 次调用回调函数(callback)构建一个序列(sequence),每次调用时回调都会收到当前的下标index,并返回一个类型为T的值,这些返回值按顺序组成最终的序列。
根据 docs/data/it-repeatby.md 中的 frontmatter 签名信息,函数签名如下:
func RepeatByT any T) iter.Seq[T]逐参数拆解:
| 参数 | 类型 | 说明 |
|---|---|---|
count | int | 回调被调用的次数,也是生成序列的元素个数 |
callback | func(index int) T | 接收当前下标、返回元素值的回调函数 |
| 返回值 | iter.Seq[T] | 惰性序列,可用for range消费 |
值得注意的关键点:
- 泛型约束为
T any:这是与同包Repeat的重要区别。Repeat要求T lo.Clonable[T](因为要复制同一个初始值),而RepeatBy对元素类型没有任何约束,任意类型均可使用; - 回调携带下标:回调的入参是
index int,因此生成的值可以依赖位置信息,例如index * 2、fmt.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.Collect将iter.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 } } } }从源码结构可以看出三个关键实现事实:
- 返回的是闭包函数:
RepeatBy并不立即计算任何值,而是返回一个类型为func(yield func(T) bool)的函数,这正是iter.Seq[T]的标准形态; for i := range count驱动循环:循环次数由count决定,i依次取0到count-1,直接作为回调参数;- 尊重 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],即i²; - 支持 break/return:测试中的
assertSeqSupportBreak(定义于 it/lo_test.go)会先以break中途退出遍历,再以return中途退出遍历,验证两者都不会 panic,从而保证序列是安全的惰性迭代器。
3.3 惰性求值带来的实战收益
由于RepeatBy返回的是惰性序列,回调只在消费时执行,因此可以:
- 按需取前几个元素:例如
count传 10000,但消费者只range前 3 个就break,回调实际只被调用 3 次,避免无谓计算; - 串联下游算子:
it包中Map、Filter、Take等函数都接受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.3Times与Repeat的姊妹关系
文档similarHelpers中还列出了core#slice#times与iter#sequence#times。slice.go 中核心包Times与RepeatBy形制相似(同样是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 count在count <= 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互为"惰性 / 急切"两种形态,并与Repeat、Times组成完整的序列生成家族。在需要生成测试数据、带序号的对象集合或作为迭代器流水线起点时,它都是高效且类型安全的选择。
延伸阅读:
- 核心包切片版
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),仅供参考