Go 實現泛型展開以及展開時計算
實現程式碼: https://github.com/v2pro/wombat/tree/master/fp
泛型展開
泛型展開不是簡單的型別替換。在C++中有模板偏特化,以及由此發展出來一系列實現編譯期計算的奇技淫巧,直到最後以constexpr變成語言的一部分。D語言的static if也是類似的,在編譯期實現了D語言的一個子集。在 Go 2.0 中即便支援了泛型,要達到D語言的高度,可能還需要很長的路要走。所以目前最佳的方案還是用程式碼生成的方案。但是純手寫的程式碼生成沒有辦法做到很複雜的泛型程式碼的組合,比如一個泛型函式呼叫另外一個泛型函式之類的。所以 wombat 的實現目標是設計一個能夠支撐大規模程式碼生成的機制,使得複雜的utility能夠被廣泛複用。這些utility可能簡單的如compare,max,複雜得如json編解碼。
最簡單的例子
定義一個泛型的函式
var compareSimpleValue = 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
}`)
測試一個泛型的函式
func init() {
generic.DynamicCompilationEnabled = true
}
func Test_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的方式載入的。這樣測試泛型程式碼就能達到和反射的實現一樣的高效。
使用一個泛型的函式
func init() {
generic.Declare(compareSimpleValue, "T", generic.Int)
}
func xxx() {
f := generic.Expand(compareSimpleValue, "T", generic.Int).
(func(int, int) int)
f(3, 4)
}
因為沒有開啟動態編譯,所以呼叫generic.Expand
會失敗。需要用 go install github.com/v2pro/wombat/cmd/codegen
編譯出程式碼生成器。然後執行
codegen -pkg path-to-your-pkg
然後會在你的包下面生成 generated.go 檔案。這樣執行時generic.Expand
就不會報錯了。
泛型展開時計算
如果需求不僅僅是支援int,還要支援int的指標。前面實現的函式模板是無法支援的。所以我們需要能夠,在泛型展開的時候進行型別判斷,選擇不同的實現。
var ByItself = 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)`)
func dispatch(typ reflect.Type) string {
switch typ.Kind() {
case reflect.Int:
return "CompareSimpleValue"
case reflect.Ptr:
return "ComparePtr"
}
panic("unsupported type: " + typ.String())
}
其中dispatch就是一個go語言實現的函式,可以在展開模板的時候被呼叫,用於選擇具體的實現。然後呼叫expand來把對應的模板再展開,然後呼叫。
遞迴展開
ComparePtr其實無法確認自己一定是呼叫CompareSimpleValue。因為可能還有**int
,以及***int
這樣的情況。所以,ComparePtr在對指標進行取消引用之後,再次呼叫CompareByItself進行遞迴展開模板。
func init() {
ByItself.ImportFunc(comparePtr)
}
var comparePtr = 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也可以加模板。寫法如下:
var Pair = 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應該是:
type IntStringPair interface {
First() int
SetFirst(val int)
Second() string
SetSecond(val string)
}
使用的程式碼需要用這個interface來建立pair的例項:
func init() {
generic.DynamicCompilationEnabled = true
}
func Test_pair(t *testing.T) {
type IntStringPair interface {
First() int
SetFirst(val int)
Second() string
SetSecond(val string)
}
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())
}
相關文章
- web 端展現報表資料時如何實現摺疊展開效果?Web
- Go 官方出品泛型教程:如何開始使用泛型Go泛型
- 泛型最佳實踐:Go泛型設計者教你如何用泛型泛型Go
- 淺談微積分以及泰勒展開
- vue實現展開全部,收起全部Vue
- iOS 實現展開TableViewCell,下拉celliOSView
- 文字超長,實現展開收起功能...
- AJAX實現留言板資訊展開
- GO語言泛型程式設計實踐Go泛型程式設計
- Go 泛型Go泛型
- Go 泛型之泛型約束Go泛型
- 活在實驗室還是實現霸權?揭開當前量子計算技術進展之謎
- jquery 實現的摺疊展開的選單jQuery
- go泛型教程Go泛型
- iOS開發--泛型iOS泛型
- jQuery實現的表格展開伸縮效果例項jQuery
- js和css3實現的扇子展開效果JSCSSS3
- Go泛型草案設計簡明指南Go泛型
- vue - antd UI table表格展開行+展開多行共存VueUI
- 十、GO程式設計模式 : 泛型程式設計Go程式設計設計模式泛型
- 簡單易懂的 Go 泛型使用和實現原理介紹Go泛型
- 計算機如何實現開根號?計算機
- RecyclerView定製:通用ItemDecoration及全展開RecyclerView的實現View
- vue實現 可展開 且 可多選table 元件封裝Vue元件封裝
- jQuery/CSS3實現圖片層疊展開特效jQueryCSSS3特效
- Android ListView收縮與展開的封裝實現AndroidView封裝
- 開源資料庫商業化加速,雲端計算助推開源軟體發展資料庫
- 康託展開+逆展開(Cantor expension)詳解+優化優化
- 在C語言中實現泛型程式設計C語言泛型程式設計
- C語言如何實現泛型程式設計?C語言泛型程式設計
- 邊緣計算技術國內外發展現狀與發展對策
- web 展現資料時如何實現行列互換Web
- 報表展現時如何實現固定表頭效果
- 計算機發展簡史計算機
- 計算機的發展史計算機
- Go泛型基礎使用Go泛型
- Go 1.17 泛型嚐鮮Go泛型
- Java & Go 泛型對比JavaGo泛型