對(duì)于是否會(huì)在以“l(fā)ess is more”為原則的golang語(yǔ)言中增加泛型(generic)特性一直頗有爭(zhēng)議瞒滴,直到官方確定泛型是go2發(fā)展的重點(diǎn)才一錘定音方咆。go 1.18中即將正式發(fā)布泛型特性露氮,當(dāng)前go 1.18beta1已經(jīng)發(fā)布峡碉,讓我們對(duì)泛型嘗嘗鮮吧瞎惫。
安裝go 1.18 beta1
當(dāng)前可有如下兩種方式來(lái)安裝go 1.18beta1葛虐。
使用go install安裝
此安裝方式要求環(huán)境中已經(jīng)安裝go胎源,然后可以通過(guò)如下命令進(jìn)行安裝。
go install golang.org/dl/go1.18beta1@latest
注意:此種方式會(huì)將安裝到$GOPATH/bin/目錄下屿脐,且安裝后的二進(jìn)制名稱為go1.18beta1而非go涕蚤。
使用binary release版本進(jìn)行安裝
此安裝方式對(duì)環(huán)境沒(méi)有要求,具體操作如下:
- 從官網(wǎng)下載本地環(huán)境對(duì)應(yīng)的go1.18beta1版本的诵;
- 參照官網(wǎng)手冊(cè)進(jìn)行安裝即可万栅。
$ go version
go version go1.18beta1 linux/amd64
從簡(jiǎn)單性上來(lái)說(shuō)推薦使用此種方式進(jìn)行安裝,我本人也是采用此方式西疤。
嘗鮮Demo
在編程實(shí)踐中我們經(jīng)常遇到從一個(gè)數(shù)組(或切片)中篩選出滿足條件元素的需求烦粒,之前大家一般會(huì)針對(duì)每一種類型的數(shù)組寫(xiě)一個(gè)篩選函數(shù)(filter),這些函數(shù)間除了名稱及類型不同外其它幾乎完全一樣代赁。對(duì)這些重復(fù)敏感的人扰她,會(huì)選擇使用反射進(jìn)行重構(gòu),但基于反射的實(shí)現(xiàn)很難進(jìn)行靜態(tài)校驗(yàn)芭碍。泛型的到來(lái)义黎,為優(yōu)雅地解決此問(wèn)題提供了便利條件。
創(chuàng)建go project
$ cd $GOPATH/src
$ mkdir hellogeneric
$ go mod init
go: creating new go.mod: module hellogeneric
非泛型時(shí)寫(xiě)法
過(guò)濾int及float64的函數(shù)如下:
func FilterInts(elems []int, predicate func(int) bool) []int {
var r []int
for _, e := range elems {
if predicate(e) {
r = append(r, e)
}
}
return r
}
func FilterFloat64s(elems []float64, predicate func(float64) bool) []float64 {
var r []float64
for _, e := range elems {
if predicate(e) {
r = append(r, e)
}
}
return r
}
相應(yīng)的main函數(shù)及運(yùn)行結(jié)果如下:
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
predicateOfInt := func(i int) bool { return i%2 == 0 }
float64s := []float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}
predicateOfFloat64 := func(f float64) bool { return f >= 5.0 }
fmt.Printf("No-Generic filters: %v and %v\n",
FilterInts(ints, predicateOfInt),
FilterFloat64s(float64s, predicateOfFloat64))
}
$ go run .
No-Generic filters: [2 4 6] and [5.5 6.6]
使用反射的寫(xiě)法
filter函數(shù)實(shí)現(xiàn)如下:
func FilterByReflect(elems, predicate interface{}) interface{} {
elemsValue := reflect.ValueOf(elems)
if elemsValue.Kind() != reflect.Slice {
panic("filter: wrong type, not a slice")
}
predicateValue := reflect.ValueOf(predicate)
if predicateValue.Kind() != reflect.Func {
panic("filter: wrong type, not a func")
}
if (predicateValue.Type().NumIn() != 1) ||
(predicateValue.Type().NumOut() != 1) ||
(predicateValue.Type().In(0) != elemsValue.Type().Elem()) ||
(predicateValue.Type().Out(0) != reflect.TypeOf(true)) {
panic("filter: wrong type, predicate must be of type func(" +
elemsValue.Elem().String() + ") bool")
}
var indexes []int
for i := 0; i < elemsValue.Len(); i++ {
if predicateValue.Call([]reflect.Value{elemsValue.Index(i)})[0].Bool() {
indexes = append(indexes, i)
}
}
r := reflect.MakeSlice(elemsValue.Type(), len(indexes), len(indexes))
for i := range indexes {
r.Index(i).Set(elemsValue.Index(indexes[i]))
}
return r.Interface()
}
相應(yīng)的main函數(shù)及運(yùn)行結(jié)果如下:
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
predicateOfInt := func(i int) bool { return i%2 == 0 }
float64s := []float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}
predicateOfFloat64 := func(f float64) bool { return f >= 5.0 }
fmt.Printf("Reflect filters: %v and %v\n",
FilterByReflect(ints, predicateOfInt),
FilterByReflect(float64s, predicateOfFloat64))
}
$ go run .
Reflect filters: [2 4 6] and [5.5 6.6]
使用泛型的寫(xiě)法
filter函數(shù)的實(shí)現(xiàn)如下:
func FilterByGeneric[V int | float64](elems []V, predicate func(V)bool) []V {
var r []V
for _, e := range elems {
if predicate(e) {
r = append(r, e)
}
}
return r
}
泛型函數(shù)相對(duì)傳統(tǒng)函數(shù)參數(shù)而言豁跑,增加了類型參數(shù)(type parameters)廉涕,這些類型參數(shù)使得函數(shù)具備了泛型能力。在調(diào)用時(shí)艇拍,也需要指定類型參數(shù)和函數(shù)參數(shù)狐蜕,由于go語(yǔ)言具有類型推斷能力通常可以省略類型參數(shù)的填寫(xiě)卸夕。
對(duì)于每一個(gè)類型參數(shù)层释,都有一個(gè)類型限定(type constraint)用于描述類型參數(shù)的元數(shù)據(jù),以指定調(diào)用時(shí)可允許的類型快集。類型限定通常代表一組類型贡羔,如本例中的int | float64
就表示允許int和float64兩種類型,對(duì)于多個(gè)泛型函數(shù)/結(jié)構(gòu)而言其類型限定可能一樣个初,此時(shí)我們就可以將類型限定單獨(dú)定義然而達(dá)到復(fù)用的目的乖寒。具體語(yǔ)法如下:
type FilterElem interface {
int | float64
}
也即可將類型限定定義為接口(interface),然后再修改相應(yīng)泛型函數(shù)定義即可:
func FilterByGeneric[V FilterElem](elems []V, predicate func(V)bool) []V
對(duì)于filter而言院溺,除了int和float64之外楣嘁,對(duì)于其它作意類型應(yīng)該也是適用的,此時(shí)我們可以使用any類型限定來(lái)表達(dá)此能力:
func FilterByGeneric[V any](elems []V, predicate func(V)bool) []V
相應(yīng)的main函數(shù)及運(yùn)行結(jié)果如下:
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
predicateOfInt := func(i int) bool { return i%2 == 0 }
float64s := []float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}
predicateOfFloat64 := func(f float64) bool { return f >= 5.0 }
fmt.Printf("Generic filters: %v and %v\n",
FilterByGeneric(ints, predicateOfInt),
FilterByGeneric(float64s, predicateOfFloat64))
}
$ go run .
Generic filters: [2 4 6] and [5.5 6.6]
后記
到這里我們就基本上對(duì)go的泛型特性進(jìn)行了嘗鮮體驗(yàn),其在一定程度上確實(shí)會(huì)使我們寫(xiě)出來(lái)的代碼更加簡(jiǎn)潔逐虚,但是否能真正在正式項(xiàng)目中使用我們還需要對(duì)此特性進(jìn)行深入學(xué)習(xí)聋溜、并對(duì)其進(jìn)行商用評(píng)估。
完整代碼見(jiàn):https://github.com/skholee/hellogeneric
references
Go Programming Patterns: Gopher China 2020 陳皓(左耳朵)