分享
  1. 首页
  2. 文章

泛型展开

v2p.ro · · 2724 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

泛型展开

Wombat Project: https://github.com/v2pro/wombat

泛型展开不是简单的类型替换。在C++中有模板偏特化,以及由此发展出来一系列实现编译期计算的奇技淫巧,直到最后以constexpr变成语言的一部分。D语言的static if也是类似的,在编译期实现了D语言的一个子集。在 Go 2.0 中即便支持了泛型,要达到D语言的高度,可能还需要很长的路要走。所以目前最佳的方案还是用代码生成的方案。但是纯手写的代码生成没有办法做到很复杂的泛型代码的组合,比如一个泛型函数调用另外一个泛型函数之类的。所以 wombat 的实现目标是设计一个能够支撑大规模代码生成的机制,使得复杂的utility能够被广泛复用。这些utility不仅仅是简单的compare,max,甚至复杂的json编解码,参数校验工具等都可以代码生成。

最简单的例子

定义一个泛型的函数

varcompareSimpleValue=generic.DefineFunc("CompareSimpleValue(val1 T, val2 T) int").Param("T","the type of value to compare").Source(`
if val1 < val2 {
	return -1
} else if val1 == val2 {
	return 0
} else {
	return 1
}`)

测试一个泛型的函数

funcinit(){generic.DynamicCompilationEnabled=true}funcTest_compare_int(t*testing.T){should:=require.New(t)f:=generic.Expand(compareSimpleValue,"T",generic.Int).(func(int,int)int)should.Equal(-1,f(3,4))should.Equal(0,f(3,3))should.Equal(1,f(4,3))}

注意,在init的时候,我们开启了动态编译。这样在测试的时候,实际上是直接在执行的时候生成代码,并用plugin的方式加载的。这样测试泛型代码就能随写随测,仿佛和用反射写的代码是一样的。

静态代码生成

wombat 虽然支持动态编译,但是不推荐上生产环境,只是用于加速泛型函数开发效率的一种手段。泛型函数的用户,还是应该用静态代码生成的方式来使用。需要静态生成,就需要在使用一个泛型的函数前,先进行声明。声明在 init() 里定义哪些模板函数的哪些类型展开会被用到

funcinit(){generic.Declare(compareSimpleValue,"T",generic.Int)}funcxxx(){f:=generic.Expand(compareSimpleValue,"T",generic.Int).(func(int,int)int)f(3,4)}

go install github.com/v2pro/wombat/cmd/codegen 编译出代码生成器。然后执行

codegen -pkg path-to-your-pkg

然后会在你的包下面生成 generated.go 文件。这样 generic.Expand 就会使用生成的代码了。如果使用之前少了对应的generic.Declare,同时又没有开启动态编译,在Expand的时候就会报错。

泛型展开时计算

如果需求不仅仅是支持int,还要支持int的指针。前面实现的函数模板是无法支持的。所以我们需要能够,在泛型展开的时候进行类型判断,选择不同的实现。

varByItself=generic.DefineFunc("CompareByItself(val1 T, val2 T) int").Param("T","the type of value to compare").Generators("dispatch",dispatch).Source(`
{{ $compare := expand (.T|dispatch) "T" .T }}
return {{$compare}}(val1, val2)`)funcdispatch(typreflect.Type)string{switchtyp.Kind(){casereflect.Int:return"CompareSimpleValue"casereflect.Ptr:return"ComparePtr"}panic("unsupported type: "+typ.String())}

其中dispatch就是一个go语言实现的函数,可以在展开模板的时候被调用,用于选择具体的实现。然后调用expand来把对应的模板再展开,然后调用。

递归展开

ComparePtr其实无法确认自己一定是调用CompareSimpleValue。因为可能还有**int,以及***int这样的情况。所以,ComparePtr在对指针进行取消引用之后,再次调用CompareByItself进行递归展开模板。

funcinit(){ByItself.ImportFunc(comparePtr)}varcomparePtr=generic.DefineFunc("ComparePtr(val1 T, val2 T) int").Param("T","the type of value to compare").ImportFunc(ByItself).Source(`
{{ $compare := expand "CompareByItself" "T" (.T|elem) }}
return {{$compare}}(*val1, *val2)`)

ByItself.ImportFunc(comparePtr) 是为了避免循环引用自身而引入的。否则两个函数就会循环引用,导致编译失败。具有了这样的函数模板化的能力,我们可以把JSON编解码这样的复杂的utility也用模板的方式写出来。

泛型容器

除了支持模板函数之外,struct也可以加模板。写法如下:

varPair=generic.DefineStruct("Pair").Source(`
{{ $T1 := .I | method "First" | returnType }}
{{ $T2 := .I | method "Second" | returnType }}
type {{.structName}} struct {
 first {{$T1|name}}
 second {{$T2|name}}
}
func (pair *{{.structName}}) SetFirst(val {{$T1|name}}) {
 pair.first = val
}
func (pair *{{.structName}}) First() {{$T1|name}} {
 return pair.first
}
func (pair *{{.structName}}) SetSecond(val {{$T2|name}}) {
 pair.second = val
}
func (pair *{{.structName}}) Second() {{$T2|name}} {
 return pair.second
}`)

其中固定了一个模板参数叫,I。这个是指模板struct需要实现的interface。比如,如果用<int,string>来展开struct,对应的interface应该是:

typeIntStringPairinterface{First()intSetFirst(valint)Second()stringSetSecond(valstring)}

使用的代码需要用这个interface来创建pair的实例:

funcinit(){generic.DynamicCompilationEnabled=true}funcTest_pair(t*testing.T){typeIntStringPairinterface{First()intSetFirst(valint)Second()stringSetSecond(valstring)}should:=require.New(t)intStringPairType:=reflect.TypeOf(new(IntStringPair)).Elem()pair:=generic.New(Pair,intStringPairType).(IntStringPair)should.Equal(0,pair.First())pair.SetFirst(1)should.Equal(1,pair.First())}

类型推断

前面 Pair 的例子中。这两行代码就类似类型推断

{{$T1:=.I|method"First"|returnType}}{{$T2:=.I|method"Second"|returnType}}

从容器的类型中取得元素的类型。

模板参数支持默认取值,比如

varByItself=generic.DefineFunc("MaxByItself(vals T) E").Param("T","array type").Param("E","array element type",func(argMapgeneric.ArgMap)interface{}{returnargMap["T"].(reflect.Type).Elem()}).ImportFunc(compare.ByItself).Source(`
{{ $compare := expand "CompareByItself" "T" .E }}
currentMax := vals[0]
for i := 1; i < len(vals); i++ {
	if {{$compare}}(vals[i], currentMax) > 0 {
		currentMax = vals[i]
	}
}
return currentMax`)

[]int 中提取 int 出来。这样泛型函数的用户使用的时候就只需要指定数组的类型,而不需要再指定元素的类型了。


有疑问加站长微信联系(非本文作者)

本文来自:v2p.ro

感谢作者:v2p.ro

查看原文:泛型展开

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2724 次点击
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏