根据Go 泛型提案的描述,Go不支持泛型方法:No parameterized methods。主要原因Go泛型的处理是在编译的时候实现的,泛型方法在编译的时候,如果没有上下文的分析推断,很难判断泛型方案该如何实例化,甚至判断不了,导致目前(Go 1.18)Go实现中不支持泛型方案。
不过,泛型方法的缺失,多多少少给程序员带来一丝丝的忧伤的情绪,在一些场景之下,使用起来特别不方便。我最近看到了几个因为缺乏泛型方法导致的问题,在本文中总结一下,和大家探讨。
有一点点让人欣慰的是,Ian Lance Taylor和Ian Lance Taylor并没有把话说绝,说不定在某个版本中,泛型方法又支持了:
So while parameterized methods seem clearly useful at first glance, we would have to decide what they mean and how to implement that.
为啥当前Go泛型不好实现泛型方法?
考虑下面一个例子,一共有四个package:
|
|
|
|
|
|
|
|
一切看起来都没有问题,但是问题是package p3
不知道p1.S
类型,整个程序中如果也没有其它地方调用p1.S.Identity
,依照现在的Go编译器的实现,是没有办法为p1.S.Identity[int]
生成对应的代码的。
是的,如果go编译器做的比较复杂,在编译的时候这个场景是可以识别出来的,但是它需要遍历整体的程序调用链以便生成全部可能的泛型方法,对编译时间和编译器复杂性带来很大的调整。另外一点,如果代码中通过反射调用的话,编译器可能会遗漏一些泛型方法的实现,这就很要命了。
如果在运行时实现呢?就需要JIT或者反射等技术,这会造成运行时性能的下降。
很难实现啊?如果规定泛型方法不能实现接口呢?那么这类的泛型方法的存在的意义是什么呢?
所以目前没有太好的手段去实现泛型方法,暂时搁置了。
如果真的有必要,你可以通过实现泛型函数来实现泛型方法,把方法的receiver当成第一个参数传递过去。
这可以解决一部分问题,但是在使用的过程中多多少少有些麻烦。
因为泛型方法的缺乏,大家在开始使用泛型的时候就遇到了麻烦,最近连续看到多篇关于这方面的问题,比如下面几个。
Facilitator模式 by rakyll
昨天rakyll写了一篇文章https://rakyll.org/generics-facilititators/,介绍她遇到的困难以及解决方式。这也是促进我把这几天看到的case总结的原因。
如果你熟悉其它编程语言,在使用orm框架的时候,可能见过下面类似的代码,实现泛型方法进行某种对象的查询:
|
|
因为Go缺乏泛型方法的实现,你不能实现泛型All
方法,那么怎么实现呢?一种方式是实现All
函数,另一种实现是实现rakyll称之为的Facilitator模式
:
|
|
函数实现让人感觉到一种无力感,一种缺乏归宿感,一种没有对象的感觉,而这种实现呢,生成了特定类型的Querier[T],All
方法就有泛型的感觉了(虽然实际是Receiver泛型)
泛型signleflight
有些同学熟悉Go官方扩展库x/sync/singleflight,这个库很好的解决大并发的时候并发访问的问题,常常用在cache访问和微服务访问的处理之中。
为了支持任意类型,它内部的实现是使用interface{}
(any
)类型来表示和处理的:
|
|
五天前,有人把它改造成泛型的方式:marwan-at-work/singleflight,上面的代码使用起来改变成如下方式:
|
|
相当于把Group改造成泛型类型,而不是实现泛型方法Do
(当然目前Go泛型也实现不了)。
这个处理和上面rakyll处理方式类型,都是生成泛型类型,通过Receiver实现泛型的方法的处理。
不过对于这种方式,有一点不好的地方就是每种类型你都得生成一个特别的对象,略显麻烦。
map reduce
更早时候关于泛型的讨论,有人提出泛型方法的缺乏导致Go实现map reduce类似的库的困难,具体在哪里提到的我已经忘记了。
比如下面的实现一个iter的map reduce:
|
|
这种情况下用户想传入任意的K
,把原先T
类型的iter转换成K
类型的iter,这种就不想其它支持泛型语言那么好实现了。