惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
月光博客
月光博客
AI
AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
GbyAI
GbyAI
L
Lohrmann on Cybersecurity
O
OpenAI News
Schneier on Security
Schneier on Security
P
Palo Alto Networks Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
L
LINUX DO - 最新话题
人人都是产品经理
人人都是产品经理
S
Security Affairs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
Arctic Wolf
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
The Hacker News
The Hacker News
Y
Y Combinator Blog
Latest news
Latest news
SecWiki News
SecWiki News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
Cisco Blogs
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
宝玉的分享
宝玉的分享
L
LINUX DO - 热门话题

鸟窝

鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 LLM 究竟是如何工作的? Go 实验特性详解 amd64 微架构级别对 Go 程序性能提升多少? Loop Engineering 实践:一次批量实现 8 个 issue,完成夔牛工具的开发 Loop Engineering 实践:我把 RDMA 开发库移植到 Go 语言,花费 239 块钱 傻瓜式RDMA高性能网络开发:从零跑到 400 Gb每秒 百度物理网络监控工具开源第二弹:毫秒级监控工具 baize,让你的网络问题无处遁形 百度网络监控工具开源第三弹:lidar — 不只是 pingmesh 别只盯着gopacket了,看看这个强大的网络库 大厂的内部工具居然开源了! 一窥百度物理网络秒级监控定位的秘密 如何构建你自己的 Agent 运行时 寻找你代码中的臭味:一个让 AI 帮你嗅出架构腐化的开源 Skill 套壳不丢人!我用Go+AI搓了一个Agent统一编排框架,ClaudeCode-Codex-Pi全被我包了 告别死锁和陈旧语法、告别性能瓶颈:三个开源 Skill,新手Gopher 秒变 Go 语言大神 SPEC和PRD的区别 SPEC和方案设计有什么区别 从需求到上线,让 AI 管理你的整个研发流程! antigravity-cli 使用 CLIProxyAPI, 让最新的 Codex 能够支持国内的各大模型 Clawpatch + codex-review:AI 代码审查工具链的正确打开方式 拆解Manus:沙盒架构深度解析 40+ Claude Code Tips: From Basics to Advanced Ralph 实验:构建 SQLite UI 了解 Manus Sandbox - 您的云计算机 Notex:一个开源 NotebookLM 替代方案的实现 拆解Manus:真正有用的深度报告的生成 Claude Code 使用 我给每个模型服务商『捐』了10块钱,只为了... Go之禅 - 基于Rob Pike思想的Go语言哲学 Codex CLI vs Gemini CLI vs Claude Code:谁是最佳选择? goskills:Claude Skills 功能强大,为我所用 一行代码使用 Claude Skill 和 deepseek langchain + MCP:如虎添翼 Linux 中网络包的一生 godotenv 库介绍 Codex CLI vs Gemini CLI vs Claude Code: Which is the Best? 使用Linux 30年了,我都不知道 ping 8.8 还能这么用? 从 AI 哪里挣钱? Go sync 包近两年发展综述 deepseek-v3.2-exp的闪电索引器
鸟窝
by smallnest · 2026-06-25 · via 鸟窝

等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go

seq 库的一行代码,从左读到右。写过 lo.Map(lo.Filter(...)) 的人,大概会愣一下。

这个库的开发我昨天晚上在微信上了做了直播,展示我如何使用Loop Engineering 从 0 构建出来。使用的是火山引擎的coding plan, GLM-5.2模型,花了 2 小时,耗费Token 7.23M。你可以查看直播回放:

你也可以访问这个库的项目地址: https://github.com/smallnest/seq, tasks目录中有需求文档和设计文档。

先看一个让你眼花的写法

要做的事很简单:把切片里的偶数挑出来,平方,求和。

用过 Go 里最火的工具库 samber/lo 的话,你多半会写成这样:

1
2
3
4
5
6
sum := lo.Sum(
lo.Map(
lo.Filter([]int{1, 2, 3, 4, 5, 6}, func(x int, _ int) bool { return x%2 == 0 }),
func(x int, _ int) int { return x * x },
),
)

发现问题了吗?

数据是从里往外流的,你的眼睛也得从里往外读。就像层层包裹的洋葱,让你的眼睛流泪

逻辑明明是「过滤 → 映射 → 求和」,写出来却是「求和(映射(过滤(...)))」,阅读顺序和数据流向正好相反。括号一层套一层,改一行还得数括号配不配对。我以前一直以为这是 lo 作者图省事,后来才知道不是。

现在看看用 seq 写同样的逻辑:

1
2
3
sum := seq.From([]int{1, 2, 3, 4, 5, 6}).
Filter(func(x int) bool { return x%2 == 0 }).
SumBy(func(x int) int { return x * x })

从左到右,数据怎么流,代码就怎么读。seq 是一个基于 Go 标准库 iter.Seq 的链式惰性集合库。


为什么 Go 等了十年才能这么写?

很多人以为 lo 的「从内往外」是作者偷懒,其实不是。这是 Go 泛型语法卡住的结果。

Go 1.27 之前,方法不能声明自己的类型参数。也就是说,一个想把 Seq[int] 变成 Seq[string].Map() 方法,在语法层面根本写不出来:

1
2
3


func (s Seq[T]) Map[U any](f func(T) U) Seq[U]

方法带不了类型参数,lo 这类库就只能把所有操作做成顶层自由函数,于是有了那串嵌套括号。不是谁懒,是当时只有这一条路。

泛型方法提案 golang/go#77273 在 Go 1.27 里落地,这条限制才算解除。链式、惰性、还能被编辑器自动补全的管道,到这时候才写得出来。seq 就是冲着这个来的。

一句大实话:seq 需要 Go 1.27,目前还是 go1.27rc1。提案官方已经接受并实现了,只差正式版发布。把这个库的Go版本设定在 1.27,是为这条方法链付的过路费。


它到底有多省心?来看一组实战

下面这些例子我都从 seq 的测试用例里抠出来的,每行都是真跑得通的。

例 1:数据分析三连——分组、去重、统计

把一串数字按奇偶分组:

1
2
3
4
5
6
7
8
groups := seq.From([]int{1, 2, 3, 4, 5, 6}).
GroupBy(func(x int) string {
if x%2 == 0 {
return "even"
}
return "odd"
})

去重之后再求和:

1
2
3
4
total := seq.Numbers(seq.From([]int{1, 2, 2, 3, 3, 3})).
Distinct().
Sum()

例 2:惰性求值,无限序列也能玩

seq 是惰性的。不调用终结操作,整条管道一行都不跑。所以你能定义一个无限序列,再只取你要的那几个:

1
2
3
4
5
6


powers := seq.Iterate(1, func(x int) int { return x * 2 }).
Take(5).
Collect()

惰性带来的真正好处是短路。FindAnyAll 拿到答案就停,后面的元素根本不碰。百万级数据里找第一个满足条件的,不会陪你白跑到底。

例 3:前缀和?一个 Scan 搞定

1
2
3
4
5

prefix := seq.From([]int{1, 2, 3, 4}).
Scan(0, func(acc, x int) int { return acc + x }).
Collect()

例 4:滑动窗口,时序数据的最爱

做监控、做时序分析的应该会喜欢,滑动窗口是内置的:

1
2
3

wins := seq.From([]int{1, 2, 3, 4, 5}).Window(3, 1)

定长分块同样开箱即用:

1
2
chunks := seq.From([]int{1, 2, 3, 4, 5}).Chunk(2)

例 5:两个序列配对——Zip 的优雅

把两个序列「拉链」拼起来,做成 map:

1
2
3
4
5
m := seq.ZipMap(
seq.From([]string{"a", "b", "c"}),
seq.From([]int{1, 2, 3}),
)

或者配对后用自定义函数合并,还能顺手改变类型:

1
2
3
4
5
6
merged := seq.ZipWith(
seq.From([]int{1, 2}),
seq.From([]string{"x", "y"}),
func(a int, b string) string { return b + string(rune('0'+a)) },
).Collect()

短的那个序列耗尽就停,完全不用手动判长度。


藏在优雅背后的「神级设计」

如果你只当它是个链式语法糖,那有点可惜。seq 真正有意思的地方,是它把 Go 泛型的那些限制,反过来当成了设计的骨架。

零成本类型转换

seq 的核心类型不是 struct 包装,而是标准库迭代器的定义类型:

1
2
type Seq[T any]     iter.Seq[T]      
type Seq2[K, V any] iter.Seq2[K, V]

意思是任何 iter.Seq[T] 都能零成本当作 seq.Seq[T] 使用,反过来也行。结果直接喂给 slices.Collectmaps.Keys 都没问题,没有任何转换开销。

一句话就能判断:它该是方法还是函数

先说清楚一个词,下面会反复用到:自由泛型函数

「自由」是相对「方法」说的,指不绑在某个接收者上的顶层函数;「泛型」指它带自己的类型参数和约束。两者合起来,就是 func Distinct[T comparable](s Seq[T]) Seq[T] 这种写法——它在包级别,自己声明 [T comparable],把序列当普通参数收进来,而不是写成 s.Distinct() 挂在 Seq 上。Go 的 slicesmaps 标准库包里那些 slices.Sort[S ...]maps.Keys[...] 全是这个形态。

为什么有些操作只能这么写,看下面这条规则就懂了。

因为 Seq[T any]T 固定成了 any,方法没办法反过来给 T 加约束。于是我干脆立了一条判断标准:

问一句:这个操作约束了 T 本身吗?约束了,就得是自由泛型函数;没约束,才能做成方法。

  • 需要 comparablecmp.Ordered 的操作(如 DistinctMaxSum)→ 自由泛型函数
  • 只用方法自带类型参数的操作(如 GroupBy[K]Map[U])→ 方法

整套 API 谁是方法、谁是函数,全按这一条推出来,没有例外。不用记,照着问一遍就知道答案,比拍脑袋舒服多了。

但是这样不就有回退到lo这个泛型库的痛点了么?

最妙的一招:用「子类型」把链式找回来,解决痛点

这条规则有个副作用:SumDistinct 变成自由泛型函数之后,管道又退回从内往外读了(Sum(Distinct(...)))。我的解法是引入三个约束型子类型,把约束提前钉在类型上:

1
2
3
4

sum := seq.Numbers(seq.From([]int{1, 2, 2, 3})).
Distinct().
Sum()

Numbers() 一进门,后面的 .Distinct().Sum() 又变回方法了。Numeric ⊂ Ordered ⊂ comparable 这层关系还能用 .Ordered().Comparable() 往下降,链一次都不断。绕是绕了点,但在 Go 现有的类型系统里,这大概是能想到的最干净的招了。


它老老实实告诉你,哪些事它不做

现在的开源项目动不动就「全能」「最强」,seq 的文档反而把不做的事一条条列了出来:

  • 不做错误处理链,在惰性迭代器上没找到优雅解,留作以后的独立提案
  • 不做并行执行,这版只有顺序惰性管道
  • 不提供「就地改原数据」的操作,seq 全程只读、不动你的原切片,每一步都产出新序列
  • 不做任意深度展平,Go 类型系统表达不了,只给一层 Flatten
  • 元组到 Tuple4 封顶,再多请自己定义 struct
  • 不是所有操作都惰性。像排序、逆序、取末尾 n 个、滑动窗口这类要回看整条序列才能出结果的操作,会先把源收集成 slice 再处理,做不到边读边算。库里专门用一个 intermediate_materializing.go 文件把它们集中放着,注释也标了「内部物化」——该惰性的惰性,该物化的老实物化,不糊弄

它甚至把 gofmtgo1.27rc1 上会对泛型方法签名误报这件事也记下来了,说明这是 RC 阶段工具链的 bug,编译和测试都过,正式版应该会消失。

一个肯告诉你边界在哪、连工具链 bug 都标注清楚的库,用起来心里有底。


上手只需一行

1
go get github.com/smallnest/seq

记得准备 Go 1.27 工具链,当前是 go1.27rc1

趁手的操作我按用途分了组,扫一眼就知道有没有你要的。

造序列
From(切片)、Of(可变参数)、EmptyRange / RangeStep(整数区间)、Repeat / RepeatInf(重复)、Generate / Iterate(无限生成)、FromChannel(channel)、FromMap(map → 键值序列)

变换 / 过滤(惰性,边读边算)
Map(换类型)、FlatMapFilterMap(过滤+变换合一)、Filter / RejectTake / DropTakeWhile / DropWhileScan(前缀累积)、Peek(旁路调试)、Intersperse(插分隔)、Concat(尾部追加)、DistinctBy(按 key 去重)

取子集 / 排序(需回看,内部物化)
TakeRight / DropRightSlice(区间)、Init / TailSortBy / ReverseChunk(定长分块)、Window(滑动窗口)、Enumerate(配索引)

聚合 / 归约 / 统计
Collect(收成 slice)、Fold / ReduceCount / CountBySumBy / MeanByMaxBy / MinBy / MaxByKey / MinByKeyJoin(拼字符串)、ForEach / ForEachIndexed

查找 / 判断(短路)
Find / FindLast / FindIndex / FindLastIndexAny / All / NoneFirst / Last / NthIsEmpty

分组 / 分区
GroupByKeyBy(建索引)、GroupCount(分组计数)、Partition(按谓词二分)、Span(首个不满足处切)

去重 / 集合运算(约束 comparable,自由泛型函数)
DistinctContains / IndexOf / LastIndexOfCountValues / ToSetUnion / Intersect / Difference / SymmetricDifferenceCompact(去零值)、Without(排除指定值)、Equal

数值 / 排序(约束 Ordered / Numeric,自由泛型函数)
Max / MinSum / Product / MeanSort

想链式调用上面这些约束操作?走子类型入口
Comparable / Ordered / Numbers 进入,之后 .Distinct().Max().Sum() 都变回方法,再用 .Ordered() / .Comparable() / .Seq() 降级

多序列组合
Zip / Zip3 / Zip4ZipWith(配对并合并)、ZipMap(配成 map)、Unzip(拆分)、Flatten(展平一层)、Concat / Interleave(交错)

键值序列 Seq2
MapValues / MapKeys / MapFilterKeys / ValuesToMap / CollectPairs / EntriesAssociate(Seq[T]Seq2)

完整签名、每条一句语义,以及设计文档都在仓库里:


写在最后

lo 是个好库,在语言能力不够的那些年,它已经做到了能做的极限。「从内往外」是它绕不过去的坎,不怪它。

seq 站在 Go 1.27 泛型方法这块新地基上,重新试了一次:Go 的集合操作能写得多顺。第一次写完 From(...).Filter(...).Map(...).Sum() 那一行,我盯着屏幕想了两秒——这真的是 Go语言吗?

觉得有意思的话,去仓库给作者点个 Star。