记录:为何使用约束子类型 SeqComparable/SeqOrdered/SeqNumeric(模拟上游 #65394 refined method constraints)
@smallnest is already working on this.
Since Jun 28, 2026.
- Dominant language
- Go
- Stars
- 66
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
背景
本库用三个「约束子类型」SeqComparable[T comparable] / SeqOrdered[T cmp.Ordered] / SeqNumeric[T Numeric](见 subtypes.go)来恢复受约束操作(Distinct/Max/Sum/...)的左到右链式调用:
seq.Numbers(seq.From([]int{1,2,3})).Distinct().Sum()
本文档记录为何要引入这套子类型机制 —— 它本质上是对上游提案 golang/go#65394(及其评论里 Merovius 称之为 "refined method constraints" 的特性)的手动模拟。一旦该提案被接受,本库可大幅删减 subtypes.go 的样板代码。
问题:Seq[T any] 上无法挂受约束方法
Seq[T] 是 iter.Seq[T] 的 defined type,T 在类型层面声明为 any。任何要求 T 本身满足约束(comparable / cmp.Ordered / Numeric)的操作 —— Distinct(要 ==)、Max/Sort(要 <)、Sum/Product(要 +/*)—— 都不能作为 Seq[T any] 的方法,因为方法签名会在不满足约束的 T 上被实例化。这正是本库「划分铁律」的一条:约束 T 本身的操作只能是自由函数。
代价:链式调用被打断,必须退回自由函数形式:
// 受约束操作是自由函数,读起来不再左到右:
seq.Sum(seq.Distinct(seq.From([]int{1,2,3})))
上游提案 #65394:refined method constraints
golang/go#65394 —— proposal: spec: allow methods on generic structs which meet subset of type constraint(OPEN,milestone Proposal,带 LanguageChangeReview 标签,截至 2026-01 仍 pending,未接受/未拒绝)—— 提议允许给方法额外施加一个比类型参数更窄的约束:
type X[T C2] struct{}
func (X[T C1]) M() {} // 仅当 T 同时满足 C1(C1 是 C2 的子约束)时,M 才进入 X[T] 的方法集
其中 golang/go#65394#issuecomment-1933627587(Merovius, 2024-02-08)是这条提案下最系统的论述,把该特性命名为 "refined method constraints"(精炼方法约束),并专门用一节 「Restricting to sub constraints」 讨论「方法约束必须是类型约束的子约束」这一限制 —— 与本库「子约束类型」的命名直接对应。该评论的核心要点:
- 可行性有据:该机制已在 Featherweight Generic Go 论文中被证明类型安全(虽非真实 Go 的完整证明,但提供了可行证据)。
- 可行性落地:编译器本就要为不同类型实参生成方法的不同实现(
Add对 int/float 不同);refined method constraints 不过是在实例化时,移除那些类型实参不满足的「精炼方法」,复用现有单态化启发式即可 —— 不需要运行时代码生成。 - 子约束检查不比现状难:验证「
C1是C2的子约束」与现有的「带类型参数的泛型调用G[T]()是否合法」是同一个检查(F[T C2]内调用G[T C1]仅当C2 ⇒ C1)。 - 解决表达式问题 / 可选接口:除泛型特化外,还顺带解决 Go 长期存在的「optional interfaces」问题(如
http.ResponseWriter的Flusher/Hijacker/Pusher组合)—— 用线性代码替代 2ⁿ 个组合类型。
本库的子类型机制 = #65394 的手动模拟
#65394 若被接受,理想写法是直接在 Seq[T] 上声明受约束方法:
// 假想:#65394 落地后
func (s Seq[T comparable]) Distinct() Seq[T] { ... }
func (s Seq[T cmp.Ordered]) Max() Optional[T] { ... }
func (s Seq[T Numeric]) Sum() T { ... }
// 于是直接:
seq.From([]int{1,2,3}).Distinct().Sum()
但在该提案落地前(且 Go 1.27 仍未含此特性),本库用手动造子类型来模拟:把约束"焊死"在类型参数上,造出 SeqComparable/SeqOrdered/SeqNumeric 三个并行 defined type,受约束操作成为各自的方法。进入子类型用自由函数(Numbers/Ordered/Comparable,约束 T),降级用方法(强约束已满足弱约束:Numeric ⊂ Ordered ⊂ comparable)。
代价:subtypes.go 的大量样板正是这套模拟的副产物 —— 每个 T-保持的中间方法(Filter/Reject/Take/Drop/TakeWhile/DropWhile/Peek,共 7 个)要在三个子类型上各重写一遍以保持链式类型,外加受约束操作在更强子类型上的继承重写。subtypes.go 因此膨胀到 400+ 行,且每新增一个 T-保持中间方法就要同步改三处。这正是 #65394 所要消除的样板。
验收 / 跟踪
- 现状已记录:受约束操作因
T: any不能是Seq[T]方法,故用SeqComparable/SeqOrdered/SeqNumeric子类型模拟 - 与上游提案的对应关系已厘清:本机制 = #65394 refined method constraints 的手动模拟
- 跟踪 golang/go#65394:若被接受并落地(Go 1.x),则本库可:
- 删除
SeqComparable/SeqOrdered/SeqNumeric三个子类型及其全部样板; - 把
Distinct/Max/Min/Sort/Sum/Product/Mean/Contains/IndexOf/... 改为Seq[T comparable]/Seq[T cmp.Ordered]/Seq[T Numeric]上的 refined method; - 入口自由函数
Numbers/Ordered/Comparable与降级方法随之移除; - 同步更新
subtypes.go(很可能整体删除)、docs/API.md、docs/en.html/zh.html。
- 删除
- 若 #65394 被拒绝,则本机制定型为长期方案,本 issue 关闭
Type
docs / tracking · 优先级 low · 关联上游 golang/go#65394
相关
- 代码:
subtypes.go(子类型定义与方法)、seq.go(Seq[T any]与划分铁律)、comparable.go/numeric.go(受约束自由函数) - 文档:
docs/API.md划分铁律、docs/en.html/zh.htmlconstrained 段 - 上游:golang/go#65394(本机制对应的提案)、#65394 的 Merovius 评论(refined method constraints 系统论述,含「Restricting to sub constraints」节)
- 同类跟踪 issue:#44(Zip/Zip3/Zip4 实例化循环,跟踪 golang/go#80172)—— 两者都是「用设计手段绕开 Go 泛型当前限制」的记录
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.