這個大學生,搶先go2實現了go的泛型

betterGo

天下苦golang久矣!git

今亡亦死,舉大計亦死,等死,死國可乎github

背景

然而生活中,接收了沒有泛型,彷佛也挺美好的樣子,直到某一天,你發現你又要對slice進行刪除操做了,明明你前幾天才作過的,明明...golang

泛型能夠不用,但泛型庫函數不能沒有,我不想再寫for了。美好的Map, Reduceuniq...,大家在哪裏?函數

因而,betterGo誕生了工具

Implement golang generic by code generating like C++ template (monomorphization)性能

經過代碼生成的方式,像C++ template同樣,實現golang泛型函數(術語叫monomorphization)測試

正如以前文章裏說的,我起了個頭,驗證了可行性後,後續就交給ui

詳情

C++的template實現的泛型,能夠簡單理解爲在編譯階段,識別出其類型,不一樣的調用都實例化到具體的類型(就是每一個泛型庫函數都根據調用者的調用參數類型生成一份代碼),go天然也能夠這樣子作。spa

但,咱們沒有影響力,不像七牛雲同樣有知名度(他們本身造了一套語法,轉譯成golang語法),造語法是不可能的:調試

  • IDE 不支持,總不能別人用了這個庫,寫的代碼編譯不了,須要調用工具轉換後才能跑吧
  • 不可能有人會用的。。

所以,須要暗度陳倉。

先提供interface{}的庫函數

golang經過 interface{}能夠實現"泛型",可是性能太差,以致於go的做者robpike本身都。。。沒眼看。

但不要緊,咱們能夠提供這些函數給用戶先使用,在開發,編譯,調試都能正常使用,使用以下:

"github.com/PioneerIncubator/betterGo/enum" //引用betterGo的庫
func mul(a, b int) (c int) {
	c = a * b
	return
}
out := enum.Reduce(a, mul, 1).(int)
複製代碼

這些測試例子可在項目的test目錄中找到。

轉譯

以後只須要調用一下咱們的工具,轉換成具體類型的函數便可,天然,咱們也會將調用方的函數改變:

out := enum.ReduceAMulInt(a, mul, 1) //這時的enum包就是用戶項目本身目錄裏的包了
複製代碼

生成的Reduce函數以下,會在調用方的目錄utils/enum/reduce.go裏:

package enum

func ReduceAMulInt(argname_1 int, argname_2 func(int, int) int, argname_3 int) int {
	lenSlice := len(argname_1)
	switch lenSlice {
	case 0:
		return 0
	case 1:
		return argname_1[1]
	}
	out := argname_2(argname_3, argname_1[0])
	next := argname_1[1]
	for i := 1; i < lenSlice; i++ {
		next = argname_1[i]
		out = argname_2(out, next)
	}
	return out
}
複製代碼

編譯

這時編譯就是特例化版本函數的二進制了。

至於生成的代碼,能夠git checkout .所有扔掉,開發依然使用interface{}版本的代碼。

後記

雖然go2 泛型明年就要出了,但也很懸- -,有興趣能夠參與開發這個項目哈,玩玩也行。

支持的函數

  • find(slice, default \ nil, fun)
    • Returns the first element for which fun returns a truthy value. If no such element is found, returns default.
  • map(slice, fun)
    • Returns a list where each element is the result of invoking fun on each corresponding element of enumerable.
  • all?(slice, fun \ fn x -> x end)
    • Returns true if fun.(element) is truthy for all elements in enumerable.
  • any?(slice, fun \ fn x -> x end)
    • Returns true if fun.(element) is truthy for at least one element in enumerable.
  • uniq_by(slice, fun)
    • Enumerates the enumerable, by removing the elements for which function fun returned duplicate elements.

項目細節:

betterGo實現了我認爲Go所缺失的部分

Real Generic

爲用戶提供了能夠直接用在代碼中的真正的interface{}

在部署以前,僅須要使用translator生成肯定類型的代碼,這種方式並不會影響你的代碼性能。

下面是已經實現的全部泛型函數:

  • enum.Reduce
  • enum.Map

實現

使用Go AST來分析你使用泛型函數的代碼,生成肯定類型的函數並替換掉你原先的調用語句

實際上所作的事

背景

如今的Go語言不支持泛型(像C++中的template、Java中的interface)

目前,爲實現泛型的需求,在Go語言中每每有以下幾種方式1

  1. Interface (with method) 優勢:無需三方庫,代碼乾淨並且通用。 缺點:須要一些額外的代碼量,以及也許沒那麼誇張的運行時開銷。
  2. Use type assertions 優勢:無需三方庫,代碼乾淨。 缺點:須要執行類型斷言,接口轉換的運行時開銷,沒有編譯時類型檢查。
  3. Reflection 優勢:乾淨 缺點:至關大的運行時開銷,沒有編譯時類型檢查。
  4. Code generation 優勢:很是乾淨的代碼(取決工具),編譯時類型檢查(有些工具甚至容許編寫針對通用代碼模板的測試),沒有運行時開銷。 缺點:構建須要第三方工具,若是一個模板爲不一樣的目標類型屢次實例化,編譯後二進制文件較大。

betterGo就是經過code generation來實現泛型

如何使用

若是你想使用betterGo來經過自動生成代碼的方式實現泛型,能夠看下面的例子:

在項目中包含了測試用例,例如,須要使用泛型的代碼是test/map/map.go,若是想用interface{} 的函數就是enum.Map 這樣子用。

若是想生成具體類型的函數,就運行這行命令:go run main.go -w -f test/map/map.go

而後你發現 test/map/map.go 改變了,enum.Map 變成了: enum.MapOriginFn(origin, fn)

而後你看項目目錄下生成了: utils/enum/map.go,就是具體類型的函數

參與項目

若是想和咱們一塊兒完成項目的開發,能夠直接看代碼,找到AST相關的包,嘗試理解相關函數的做用,很容易就能夠理解這個項目以及代碼了。

若是想從理論出發的話,能夠簡單看看這本書:github.com/chai2010/go… ,其實他也就是把AST包裏的代碼簡單講講。

想參與具體開發能夠參考項目接下來的TODO List

技術思路

  1. 導入須要操做的文件/目錄

  2. 經過AST進行語法分析

    AST能分析出每條語句的性質,如:

    • GenDecl (通常聲明):包括import、常量聲明、變量聲明、類型聲明
    • AssignStmt(賦值語句):包括賦值語句和短的變量聲明(a := 1)
    • FuncDecl(函數聲明)
    • TypeAssertExpr(類型斷言)
    • CallExpr(函數調用語句)
  3. 當分析到包含變量的值/類型的語句時(AssignStmtFuncDecl)會對變量的值和類型進行記錄,並創建兩者之間的映射關係,以便於在後續環節中可以經過變量名獲取變量的類型

  4. 當發現函數調用語句(CallExpr)時,會檢查該函數是否爲咱們提供的函數,若是是,則經過上一步中記錄的參數名對應的類型生成專門處理該類型的一份代碼,並存儲到指定路徑下(若是以前已經生成過相同類型的代碼則不重複生成)

  5. 將原代碼中的原來的函數調用語句替換成新的函數調用語句,使其調用上一步中新生成的函數,並更新import的包

Reference

[1] Go有什麼泛型的實現方法? - 達的回答 - 知乎
相關文章
相關標籤/搜索