Go 1.27 给具体方法加了类型参数:List.Map 能链式写了,接口方法仍然不行
Go 1.27 的语言变更记录:go.dev/blog/generic-methods(Mark Freeman,2026 年 8 月 26 日)。
Go 1.18 引入泛型时,类型和函数都拿到了类型参数,方法没有。当时的泛型提案给的理由是:泛型接口方法难以高效实现,因此连具体(非接口)方法也一并被排除在外;方法主要被当作实现接口的手段。Go 1.27 改变了这一取舍,具体方法可以声明自己的类型参数。
链表上的转换
type List[E any] struct {
elem E
next *List[E]
}
func NewList[E any](elems ...E) List[E]
func (List[E]) String() string
把 List[int] 转成 List[string],方法签名是:
func (List[E]) ToString(f func(E) string) List[string]
传入 strconv.Itoa,或者对字节序列用 hex.EncodeToString / base64 编码。
这里的问题是:参数化的是 List,泛化的是来源类型 E,目标类型 R 并没有被参数化。Go 1.18 只能写成包级泛型函数:
func MapList[E, R any](l List[E], f func(E) R) List[R]
MapList 会进入包作用域。当包里多种数据类型都需要 map 操作时,包命名空间会变得拥挤。链式调用也只能由内向外写:
MapList(MapList(NewList(0, 2, 4), add(2)), divideBy(2))
方法版 Map
Go 1.27 可以把它写成方法:
func (List[E]) Map[R any](f func(E) R) List[R]
调用就变成从左到右阅读:
NewList(0, 2, 4).Map(add(2)).Map(divideBy(2))
接收者类型 List[E] 绑定来源元素类型,方法自己的 [R any] 绑定目标类型,两者互不干扰。可读性上的差别在于:操作挂在数据结构上,不再散落在包级命名空间里;阅读顺序和书写顺序一致。
用 method expression 还原函数式写法
如果确实需要函数式的写法,可以取方法表达式:
f := List[int].Map[int]
f(f(NewList(0, 2, 4), add(2)), divideBy(2))
泛型方法和其他泛型实体一样,必须先显式或隐式实例化,才能被调用,或转换成函数值。
接口方法仍然不能带类型参数
下面这种声明是不允许的:
type I interface {
M[P any]() // 编译错误
}
因此,T 的泛型方法无法参与接口实现。接口实现是某个(可能已经实例化的)类型的属性,而不是某个具体方法的属性:T 声明的是泛型(未实例化)的 T.M,而不是 T.M[int]。
原因在编译模型。接口值是一个盒子,里面装的是任意实现了该接口的类型。包 p 里的:
func F(i I) { i.M[int]() }
单独编译时,编译 main 包的一方无从知道 p.F 会对 T{} 做什么,包括会把 T.M 实例化成哪些类型实参。要覆盖任意用法,编译器就得用所有可能的类型实参去实例化 T.M,这不现实。
另一条路是把方法参数装箱,按约束接口传递,让所有实例共享同一份代码。代价是间接调用开销,而且即使是直接调用一个已实例化的方法,也要承担这笔开销。
结论就一句:Go 1.27 支持具体方法的类型参数,不支持泛型接口方法。