等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go
本机暂存
<p><code>seq</code> 库的一行代码,从左读到右。写过 <code>lo.Map(lo.Filter(...))</code> 的人,大概会愣一下。</p><p> 这个库的开发我昨天晚上在微信上了做了直播,展示我如何使用Loop Engineering 从 0 构建出来。使用的是火山引擎的coding plan, GLM-5.2模型,花了 2 小时,耗费Token 7.23M。你可以查看直播回放:</p><p>你也可以访问这个库的项目地址: <a href="https://github.com/smallnest/seq%EF%BC%8C">https://github.com/smallnest/seq,</a> tasks目录中有需求文档和设计文档。</p><span id="more"></span><h2 id="先看一个让你眼花的写法"><a href="#先看一个让你眼花的写法" class="headerlink" title="先看一个让你眼花的写法"></a>先看一个让你眼花的写法</h2><p>要做的事很简单:把切片里的偶数挑出来,平方,求和。</p><p>用过 Go 里最火的工具库 <a href="https://github.com/samber/lo"><code>samber/lo</code></a> 的话,你多半会写成这样:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sum := lo.Sum(</span><br><span class="line"> lo.Map(</span><br><span class="line"> lo.Filter([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>}, <span class="function"><span class="keyword">func</span><span class="params">(x <span class="type">int</span>, _ <span class="type">int</span>)</span></span> <span class="type">bool</span> { <span class="keyword">return</span> x%<span class="number">2</span> == <span class="number">0</span> }),</span><br><span class="line"> <span class="function"><span class="keyword">func</span><span class="params">(x <span class="type">int</span>, _ <span class="type">int</span>)</span></span> <span class="type">int</span> { <span class="keyword">return</span> x * x },</span><br><span class="line"> ),</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>发现问题了吗?</p><p><strong>数据是从里往外流的,你的眼睛也得从里往外读。就像层层包裹的洋葱,让你的眼睛流泪</strong></p><img src="/2026/06/25/go-seq-chained-pipeline-like-scala/image-20260625000939939.png" class=""><p>逻辑明明是<code>「过滤 → 映射 → 求和」</code>,写出来却是<code>「求和(映射(过滤(...)))」</code>,阅读顺序和数据流向正好相反。括号一层套一层,改一行还得数括号配不配对。我以前一直以为这是 <code>lo</code> 作者图省事,后来才知道不是。</p><p>现在看看用 <code>seq</code> 写同样的逻辑:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sum := seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>}).</span><br><span class="line"> Filter(<span class="function"><span class="keyword">func</span><span class="params">(x <span class="type">int</span>)</span></span> <span class="type">bool</span> { <span class="keyword">return</span> x%<span class="number">2</span> == <span class="number">0</span> }).</span><br><span class="line"> SumBy(<span class="function"><span class="keyword">func</span><span class="params">(x <span class="type">int</span>)</span></span> <span class="type">int</span> { <span class="keyword">return</span> x * x })</span><br></pre></td></tr></table></figure><p>从左到右,数据怎么流,代码就怎么读。<code>seq</code> 是一个基于 Go 标准库 <code>iter.Seq</code> 的链式惰性集合库。</p><hr><h2 id="为什么-Go-等了十年才能这么写"><a href="#为什么-Go-等了十年才能这么写" class="headerlink" title="为什么 Go 等了十年才能这么写?"></a>为什么 Go 等了十年才能这么写?</h2><p>很多人以为 <code>lo</code> 的「从内往外」是作者偷懒,其实不是。这是 Go 泛型语法卡住的结果。</p><p>Go 1.27 之前,方法不能声明自己的类型参数。也就是说,一个想把 <code>Seq[int]</code> 变成 <code>Seq[string]</code> 的 <code>.Map()</code> 方法,在语法层面根本写不出来:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Go 1.27 之前:这一行编译不过。</span></span><br><span class="line"><span class="comment">// 编译器会告诉你:方法不允许有自己的 [U any]。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s Seq[T])</span></span> Map[U any](f <span class="function"><span class="keyword">func</span><span class="params">(T)</span></span> U) Seq[U]</span><br></pre></td></tr></table></figure><p>方法带不了类型参数,<code>lo</code> 这类库就只能把所有操作做成顶层自由函数,于是有了那串嵌套括号。不是谁懒,是当时只有这一条路。</p><p><a href="https://github.com/golang/go/issues/77273">泛型方法提案 golang/go#77273</a> 在 Go 1.27 里落地,这条限制才算解除。链式、惰性、还能被编辑器自动补全的管道,到这时候才写得出来。<code>seq</code> 就是冲着这个来的。</p><blockquote><p>一句大实话:<code>seq</code> 需要 Go 1.27,目前还是 <code>go1.27rc1</code>。提案官方已经接受并实现了,只差正式版发布。把这个库的Go版本设定在 1.27,是为这条方法链付的过路费。</p></blockquote><hr><h2 id="它到底有多省心-来看一组实战"><a href="#它到底有多省心-来看一组实战" class="headerlink" title="它到底有多省心?来看一组实战"></a>它到底有多省心?来看一组实战</h2><p>下面这些例子我都从 <code>seq</code> 的测试用例里抠出来的,每行都是真跑得通的。</p><h3 id="例-1-数据分析三连——分组、去重、统计"><a href="#例-1-数据分析三连——分组、去重、统计" class="headerlink" title="例 1:数据分析三连——分组、去重、统计"></a>例 1:数据分析三连——分组、去重、统计</h3><p>把一串数字按奇偶分组:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">groups := seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>}).</span><br><span class="line"> GroupBy(<span class="function"><span class="keyword">func</span><span class="params">(x <span class="type">int</span>)</span></span> <span class="type">string</span> {</span><br><span class="line"> <span class="keyword">if</span> x%<span class="number">2</span> == <span class="number">0</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"even"</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"odd"</span></span><br><span class="line"> })</span><br><span class="line"><span class="comment">// groups => map[string][]int{"even": {2,4,6}, "odd": {1,3,5}}</span></span><br></pre></td></tr></table></figure><p>去重之后再求和:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">total := seq.Numbers(seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>})).</span><br><span class="line"> Distinct().</span><br><span class="line"> Sum()</span><br><span class="line"><span class="comment">// total => 6 (1+2+3)</span></span><br></pre></td></tr></table></figure><h3 id="例-2-惰性求值-无限序列也能玩"><a href="#例-2-惰性求值-无限序列也能玩" class="headerlink" title="例 2:惰性求值,无限序列也能玩"></a>例 2:惰性求值,无限序列也能玩</h3><img src="/2026/06/25/go-seq-chained-pipeline-like-scala/image-20260625001059545.png" class=""><p><code>seq</code> 是惰性的。不调用终结操作,整条管道一行都不跑。所以你能定义一个无限序列,再只取你要的那几个:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 从 1 开始,每次翻倍:1, 2, 4, 8, 16...</span></span><br><span class="line"><span class="comment">// 这是个无限序列,但 Take(5) 只会驱动它跑 5 次</span></span><br><span class="line">powers := seq.Iterate(<span class="number">1</span>, <span class="function"><span class="keyword">func</span><span class="params">(x <span class="type">int</span>)</span></span> <span class="type">int</span> { <span class="keyword">return</span> x * <span class="number">2</span> }).</span><br><span class="line"> Take(<span class="number">5</span>).</span><br><span class="line"> Collect()</span><br><span class="line"><span class="comment">// powers => [1, 2, 4, 8, 16]</span></span><br></pre></td></tr></table></figure><p>惰性带来的真正好处是短路。<code>Find</code>、<code>Any</code>、<code>All</code> 拿到答案就停,后面的元素根本不碰。百万级数据里找第一个满足条件的,不会陪你白跑到底。</p><h3 id="例-3-前缀和-一个-Scan-搞定"><a href="#例-3-前缀和-一个-Scan-搞定" class="headerlink" title="例 3:前缀和?一个 Scan 搞定"></a>例 3:前缀和?一个 Scan 搞定</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 发射每一步的累积值(含初始值),典型的前缀和</span></span><br><span class="line">prefix := seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>}).</span><br><span class="line"> Scan(<span class="number">0</span>, <span class="function"><span class="keyword">func</span><span class="params">(acc, x <span class="type">int</span>)</span></span> <span class="type">int</span> { <span class="keyword">return</span> acc + x }).</span><br><span class="line"> Collect()</span><br><span class="line"><span class="comment">// prefix => [0, 1, 3, 6, 10]</span></span><br></pre></td></tr></table></figure><h3 id="例-4-滑动窗口-时序数据的最爱"><a href="#例-4-滑动窗口-时序数据的最爱" class="headerlink" title="例 4:滑动窗口,时序数据的最爱"></a>例 4:滑动窗口,时序数据的最爱</h3><p>做监控、做时序分析的应该会喜欢,滑动窗口是内置的:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 窗口大小 3,步长 1</span></span><br><span class="line">wins := seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}).Window(<span class="number">3</span>, <span class="number">1</span>)</span><br><span class="line"><span class="comment">// => [1,2,3], [2,3,4], [3,4,5]</span></span><br></pre></td></tr></table></figure><p>定长分块同样开箱即用:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">chunks := seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}).Chunk(<span class="number">2</span>)</span><br><span class="line"><span class="comment">// => [1,2], [3,4], [5]</span></span><br></pre></td></tr></table></figure><h3 id="例-5-两个序列配对——Zip-的优雅"><a href="#例-5-两个序列配对——Zip-的优雅" class="headerlink" title="例 5:两个序列配对——Zip 的优雅"></a>例 5:两个序列配对——Zip 的优雅</h3><p>把两个序列「拉链」拼起来,做成 map:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">m := seq.ZipMap(</span><br><span class="line"> seq.From([]<span class="type">string</span>{<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>}),</span><br><span class="line"> seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}),</span><br><span class="line">)</span><br><span class="line"><span class="comment">// m => map[string]int{"a": 1, "b": 2, "c": 3}</span></span><br></pre></td></tr></table></figure><p>或者配对后用自定义函数合并,还能顺手改变类型:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">merged := seq.ZipWith(</span><br><span class="line"> seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>}),</span><br><span class="line"> seq.From([]<span class="type">string</span>{<span class="string">"x"</span>, <span class="string">"y"</span>}),</span><br><span class="line"> <span class="function"><span class="keyword">func</span><span class="params">(a <span class="type">int</span>, b <span class="type">string</span>)</span></span> <span class="type">string</span> { <span class="keyword">return</span> b + <span class="type">string</span>(<span class="type">rune</span>(<span class="string">'0'</span>+a)) },</span><br><span class="line">).Collect()</span><br><span class="line"><span class="comment">// merged => ["x1", "y2"]</span></span><br></pre></td></tr></table></figure><p>短的那个序列耗尽就停,完全不用手动判长度。</p><hr><h2 id="藏在优雅背后的「神级设计」"><a href="#藏在优雅背后的「神级设计」" class="headerlink" title="藏在优雅背后的「神级设计」"></a>藏在优雅背后的「神级设计」</h2><p>如果你只当它是个链式语法糖,那有点可惜。<code>seq</code> 真正有意思的地方,是它把 Go 泛型的那些限制,反过来当成了设计的骨架。</p><h3 id="零成本类型转换"><a href="#零成本类型转换" class="headerlink" title="零成本类型转换"></a>零成本类型转换</h3><p><code>seq</code> 的核心类型不是 struct 包装,而是标准库迭代器的定义类型:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Seq[T any] iter.Seq[T] <span class="comment">// 本质就是 func(yield func(T) bool)</span></span><br><span class="line"><span class="keyword">type</span> Seq2[K, V any] iter.Seq2[K, V]</span><br></pre></td></tr></table></figure><p>意思是任何 <code>iter.Seq[T]</code> 都能零成本当作 <code>seq.Seq[T]</code> 使用,反过来也行。结果直接喂给 <code>slices.Collect</code>、<code>maps.Keys</code> 都没问题,没有任何转换开销。</p><h3 id="一句话就能判断-它该是方法还是函数"><a href="#一句话就能判断-它该是方法还是函数" class="headerlink" title="一句话就能判断:它该是方法还是函数"></a>一句话就能判断:它该是方法还是函数</h3><img src="/2026/06/25/go-seq-chained-pipeline-like-scala/image-20260625001247515.png" class=""><p>先说清楚一个词,下面会反复用到:<strong>自由泛型函数</strong>。</p><p>「自由」是相对「方法」说的,指不绑在某个接收者上的顶层函数;「泛型」指它带自己的类型参数和约束。两者合起来,就是 <code>func Distinct[T comparable](s Seq[T]) Seq[T]</code> 这种写法——它在包级别,自己声明 <code>[T comparable]</code>,把序列当普通参数收进来,而不是写成 <code>s.Distinct()</code> 挂在 <code>Seq</code> 上。Go 的 <code>slices</code>、<code>maps</code> 标准库包里那些 <code>slices.Sort[S ...]</code>、<code>maps.Keys[...]</code> 全是这个形态。</p><p>为什么有些操作只能这么写,看下面这条规则就懂了。</p><p>因为 <code>Seq[T any]</code> 把 <code>T</code> 固定成了 <code>any</code>,方法没办法反过来给 <code>T</code> 加约束。于是我干脆立了一条判断标准:</p><blockquote><p>问一句:这个操作约束了 <code>T</code> 本身吗?约束了,就得是自由泛型函数;没约束,才能做成方法。</p></blockquote><ul><li>需要 <code>comparable</code>、<code>cmp.Ordered</code> 的操作(如 <code>Distinct</code>、<code>Max</code>、<code>Sum</code>)→ 自由泛型函数</li><li>只用方法自带类型参数的操作(如 <code>GroupBy[K]</code>、<code>Map[U]</code>)→ 方法</li></ul><p>整套 API 谁是方法、谁是函数,全按这一条推出来,没有例外。不用记,照着问一遍就知道答案,比拍脑袋舒服多了。</p><p>但是这样不就有回退到<code>lo</code>这个泛型库的痛点了么?</p><h3 id="最妙的一招-用「子类型」把链式找回来,解决痛点"><a href="#最妙的一招-用「子类型」把链式找回来,解决痛点" class="headerlink" title="最妙的一招:用「子类型」把链式找回来,解决痛点"></a>最妙的一招:用「子类型」把链式找回来,解决痛点</h3><img src="/2026/06/25/go-seq-chained-pipeline-like-scala/image-20260625001748970.png" class=""><p>这条规则有个副作用:<code>Sum</code>、<code>Distinct</code> 变成自由泛型函数之后,管道又退回从内往外读了(<code>Sum(Distinct(...))</code>)。我的解法是引入三个约束型子类型,把约束提前钉在类型上:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用 Numbers/Ordered/Comparable 进入约束世界,之后全程方法链</span></span><br><span class="line">sum := seq.Numbers(seq.From([]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">3</span>})).</span><br><span class="line"> Distinct().</span><br><span class="line"> Sum()</span><br></pre></td></tr></table></figure><p><code>Numbers()</code> 一进门,后面的 <code>.Distinct()</code>、<code>.Sum()</code> 又变回方法了。<code>Numeric ⊂ Ordered ⊂ comparable</code> 这层关系还能用 <code>.Ordered()</code>、<code>.Comparable()</code> 往下降,链一次都不断。绕是绕了点,但在 Go 现有的类型系统里,这大概是能想到的最干净的招了。</p><hr><h2 id="它老老实实告诉你-哪些事它不做"><a href="#它老老实实告诉你-哪些事它不做" class="headerlink" title="它老老实实告诉你,哪些事它不做"></a>它老老实实告诉你,哪些事它不做</h2><p>现在的开源项目动不动就「全能」「最强」,<code>seq</code> 的文档反而把不做的事一条条列了出来:</p><ul><li>不做错误处理链,在惰性迭代器上没找到优雅解,留作以后的独立提案</li><li>不做并行执行,这版只有顺序惰性管道</li><li>不提供「就地改原数据」的操作,<code>seq</code> 全程只读、不动你的原切片,每一步都产出新序列</li><li>不做任意深度展平,Go 类型系统表达不了,只给一层 <code>Flatten</code></li><li>元组到 <code>Tuple4</code> 封顶,再多请自己定义 struct</li><li>不是所有操作都惰性。像排序、逆序、取末尾 n 个、滑动窗口这类要回看整条序列才能出结果的操作,会先把源收集成 slice 再处理,做不到边读边算。库里专门用一个 <code>intermediate_materializing.go</code> 文件把它们集中放着,注释也标了「内部物化」——该惰性的惰性,该物化的老实物化,不糊弄</li></ul><img src="/2026/06/25/go-seq-chained-pipeline-like-scala/image-20260625001924090.png" class=""><p>它甚至把 <code>gofmt</code> 在 <code>go1.27rc1</code> 上会对泛型方法签名误报这件事也记下来了,说明这是 RC 阶段工具链的 bug,编译和测试都过,正式版应该会消失。</p><p>一个肯告诉你边界在哪、连工具链 bug 都标注清楚的库,用起来心里有底。</p><hr><h2 id="上手只需一行"><a href="#上手只需一行" class="headerlink" title="上手只需一行"></a>上手只需一行</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go get github.com/smallnest/seq</span><br></pre></td></tr></table></figure><blockquote><p>记得准备 Go 1.27 工具链,当前是 <code>go1.27rc1</code>。</p></blockquote><p>趁手的操作我按用途分了组,扫一眼就知道有没有你要的。</p><p><strong>造序列</strong><br><code>From</code>(切片)、<code>Of</code>(可变参数)、<code>Empty</code>、<code>Range</code> / <code>RangeStep</code>(整数区间)、<code>Repeat</code> / <code>RepeatInf</code>(重复)、<code>Generate</code> / <code>Iterate</code>(无限生成)、<code>FromChannel</code>(channel)、<code>FromMap</code>(map → 键值序列)</p><p><strong>变换 / 过滤(惰性,边读边算)</strong><br><code>Map</code>(换类型)、<code>FlatMap</code>、<code>FilterMap</code>(过滤+变换合一)、<code>Filter</code> / <code>Reject</code>、<code>Take</code> / <code>Drop</code>、<code>TakeWhile</code> / <code>DropWhile</code>、<code>Scan</code>(前缀累积)、<code>Peek</code>(旁路调试)、<code>Intersperse</code>(插分隔)、<code>Concat</code>(尾部追加)、<code>DistinctBy</code>(按 key 去重)</p><p><strong>取子集 / 排序(需回看,内部物化)</strong><br><code>TakeRight</code> / <code>DropRight</code>、<code>Slice</code>(区间)、<code>Init</code> / <code>Tail</code>、<code>SortBy</code> / <code>Reverse</code>、<code>Chunk</code>(定长分块)、<code>Window</code>(滑动窗口)、<code>Enumerate</code>(配索引)</p><p><strong>聚合 / 归约 / 统计</strong><br><code>Collect</code>(收成 slice)、<code>Fold</code> / <code>Reduce</code>、<code>Count</code> / <code>CountBy</code>、<code>SumBy</code> / <code>MeanBy</code>、<code>MaxBy</code> / <code>MinBy</code> / <code>MaxByKey</code> / <code>MinByKey</code>、<code>Join</code>(拼字符串)、<code>ForEach</code> / <code>ForEachIndexed</code></p><p><strong>查找 / 判断(短路)</strong><br><code>Find</code> / <code>FindLast</code> / <code>FindIndex</code> / <code>FindLastIndex</code>、<code>Any</code> / <code>All</code> / <code>None</code>、<code>First</code> / <code>Last</code> / <code>Nth</code>、<code>IsEmpty</code></p><p><strong>分组 / 分区</strong><br><code>GroupBy</code>、<code>KeyBy</code>(建索引)、<code>GroupCount</code>(分组计数)、<code>Partition</code>(按谓词二分)、<code>Span</code>(首个不满足处切)</p><p><strong>去重 / 集合运算(约束 <code>comparable</code>,自由泛型函数)</strong><br><code>Distinct</code>、<code>Contains</code> / <code>IndexOf</code> / <code>LastIndexOf</code>、<code>CountValues</code> / <code>ToSet</code>、<code>Union</code> / <code>Intersect</code> / <code>Difference</code> / <code>SymmetricDifference</code>、<code>Compact</code>(去零值)、<code>Without</code>(排除指定值)、<code>Equal</code></p><p><strong>数值 / 排序(约束 <code>Ordered</code> / <code>Numeric</code>,自由泛型函数)</strong><br><code>Max</code> / <code>Min</code>、<code>Sum</code> / <code>Product</code> / <code>Mean</code>、<code>Sort</code></p><p><strong>想链式调用上面这些约束操作?走子类型入口</strong><br><code>Comparable</code> / <code>Ordered</code> / <code>Numbers</code> 进入,之后 <code>.Distinct()</code>、<code>.Max()</code>、<code>.Sum()</code> 都变回方法,再用 <code>.Ordered()</code> / <code>.Comparable()</code> / <code>.Seq()</code> 降级</p><p><strong>多序列组合</strong><br><code>Zip</code> / <code>Zip3</code> / <code>Zip4</code>、<code>ZipWith</code>(配对并合并)、<code>ZipMap</code>(配成 map)、<code>Unzip</code>(拆分)、<code>Flatten</code>(展平一层)、<code>Concat</code> / <code>Interleave</code>(交错)</p><p><strong>键值序列 <code>Seq2</code></strong><br><code>MapValues</code> / <code>MapKeys</code> / <code>Map</code>、<code>Filter</code>、<code>Keys</code> / <code>Values</code>、<code>ToMap</code> / <code>CollectPairs</code> / <code>Entries</code>、<code>Associate</code>(<code>Seq[T]</code> → <code>Seq2</code>)</p><p>完整签名、每条一句语义,以及设计文档都在仓库里:</p><ul><li>仓库:<a href="https://github.com/smallnest/seq">https://github.com/smallnest/seq</a></li></ul><hr><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p><code>lo</code> 是个好库,在语言能力不够的那些年,它已经做到了能做的极限。「从内往外」是它绕不过去的坎,不怪它。</p><p><code>seq</code> 站在 Go 1.27 泛型方法这块新地基上,重新试了一次:Go 的集合操作能写得多顺。第一次写完 <code>From(...).Filter(...).Map(...).Sum()</code> 那一行,我盯着屏幕想了两秒——这真的是 Go语言吗?</p><p>觉得有意思的话,去仓库给作者点个 Star。</p>
同分类推荐文章
- Go 实验特性详解 (2026-06-21 10:05:27)
- amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)
- Loop Engineering 实践:我把 RDMA 开发库移植到 Go 语言,花费 239 块钱 (2026-06-17 04:00:24)
建议继续学习
- Go Reflect 性能 (累计阅读 14,144)
- 面向“接口”编程和面向“实现”编程 (累计阅读 13,899)
- 一种基于长连接的社交游戏服务器程序构架 (累计阅读 7,491)
- 从Go看,语言设计(一) (累计阅读 6,156)
- go-kit 入门(一) (累计阅读 4,758)
- 分布式存储Seaweedfs源码分析 (累计阅读 4,741)
- 为什么我们要使用Go语言以及如何使用它的 (累计阅读 4,575)
- Go 语言初步 (累计阅读 4,492)
- 程序员的“横向发展” (累计阅读 4,132)
- ZeroMQ 的模式 (累计阅读 4,051)