關於 Go 中的實用函數
目標:讓 Go 中支持類似下面這樣的函數
func Max(collection ...interface{}) interface{}n
問題:如果用反射實現的話,效率是問題。
解決方案:json.Unmarshal 就是用反射實現的,jsoniter 通過用 unsafe.Pointer 加上緩存的 decoder 實現了6倍的速度提升。所以嘗試用同樣的技術原理,寫一個概念驗證的原型 v2pro/wombat
實現的API類似這樣
import (n "testing"n "github.com/stretchr/testify/require"n "github.com/v2pro/plz"n)nnfunc Test_max_min(t *testing.T) {n should := require.New(t)n should.Equal(3, plz.Max(1, 3, 2))n should.Equal(1, plz.Min(1, 3, 2))nn type User struct {n Score intn }n should.Equal(User{3}, plz.Max(n User{1}, User{3}, User{2},n "Score"))n}n
其中的原理是從 interface{} 中提取 unsafe.Pointer。 然後用 Accessor 獲得具體的值。這個 Accessor 的概念是和類對應的,而不是和值對應的。也就是相當於 type.GetIntValue(interface{}) 這樣的意思。這個在 Java 的反射 API 中是支持的,而 Go 沒有提供這樣的 API。利用 Accessor 我們可以一次性計算好整個任務,然後緩存起來。這樣運行期的成本大概就是虛函數調用的成本。
Accessor 的介面定義
type Accessor interface {n // === static ===n fmt.GoStringern Kind() Kindn // mapn Key() Accessorn // array/mapn Elem() Accessorn // structn NumField() intn Field(index int) StructFieldn // array/structn RandomAccessible() booln New() (interface{}, Accessor)nn // === runtime ===n IsNil(ptr unsafe.Pointer) booln // variantn VariantElem(ptr unsafe.Pointer) (elem unsafe.Pointer, elemAccessor Accessor)n InitVariant(ptr unsafe.Pointer, template Accessor) (elem unsafe.Pointer, elemAccessor Accessor)n // mapn MapIndex(ptr unsafe.Pointer, key unsafe.Pointer) (elem unsafe.Pointer) // only when random accessiblen SetMapIndex(ptr unsafe.Pointer, key unsafe.Pointer, elem unsafe.Pointer) // only when random accessiblen IterateMap(ptr unsafe.Pointer, cb func(key unsafe.Pointer, elem unsafe.Pointer) bool)n FillMap(ptr unsafe.Pointer, cb func(filler MapFiller))n // array/structn ArrayIndex(ptr unsafe.Pointer, index int) (elem unsafe.Pointer) // only when random accessiblen IterateArray(ptr unsafe.Pointer, cb func(index int, elem unsafe.Pointer) bool)n FillArray(ptr unsafe.Pointer, cb func(filler ArrayFiller))n // primitivesn Skip(ptr unsafe.Pointer) // when the value is not neededn String(ptr unsafe.Pointer) stringn SetString(ptr unsafe.Pointer, val string)n Bool(ptr unsafe.Pointer) booln SetBool(ptr unsafe.Pointer, val bool)n Int(ptr unsafe.Pointer) intn SetInt(ptr unsafe.Pointer, val int)n Int8(ptr unsafe.Pointer) int8n SetInt8(ptr unsafe.Pointer, val int8)n Int16(ptr unsafe.Pointer) int16n SetInt16(ptr unsafe.Pointer, val int16)n Int32(ptr unsafe.Pointer) int32n SetInt32(ptr unsafe.Pointer, val int32)n Int64(ptr unsafe.Pointer) int64n SetInt64(ptr unsafe.Pointer, val int64)n Uint(ptr unsafe.Pointer) uintn SetUint(ptr unsafe.Pointer, val uint)n Uint8(ptr unsafe.Pointer) uint8n SetUint8(ptr unsafe.Pointer, val uint8)n Uint16(ptr unsafe.Pointer) uint16n SetUint16(ptr unsafe.Pointer, val uint16)n Uint32(ptr unsafe.Pointer) uint32n SetUint32(ptr unsafe.Pointer, val uint32)n Uint64(ptr unsafe.Pointer) uint64n SetUint64(ptr unsafe.Pointer, val uint64)n Float32(ptr unsafe.Pointer) float32n SetFloat32(ptr unsafe.Pointer, val float32)n Float64(ptr unsafe.Pointer) float64n SetFloat64(ptr unsafe.Pointer, val float64)n}n
利用這個 Accessor 可以干很多事情,除了各種函數式編程常用的utility(map/filter/sorted/...)之外。還可以實現一個 plz.Copy 的函數
func Copy(dst, src interface{}) errorn
Copy 可以用於各種對象綁定的場景
- Go 不同類型對象之間的值拷貝(struct&map互相轉換,兼容指針)
- JSON 編解碼
- 拷貝 http.Request 到我的 struct 上
- 拷貝 sql rows 到我的 struct 上
- Mysql/thrift/redis 等其他協議的編解碼
還可以用來實現 plz.Validate 的函數
func Validate(obj interface{}) errorn
甚至有可能的話,還可以把 .net 的 linq 的概念拿過來
func Query(obj interface{}, query string) (result interface{}, err error)n
當然這個工作量非常浩大,比一個JSON解析庫繁瑣得多。現在只實現了幾個概念原型:
- Max/Min
- Go對象拷貝
- 用 plz.Copy 實現 JSON 編解碼
- 用 plz.Copy 實現 HTTP 參數綁定
用興趣的朋友可以來發issue:v2pro/wombat
推薦閱讀: