本文目錄一覽:
嘗試用golang 1.18泛型實現orm
這幾天golang社區對泛型的討論非常多的,一片熱火朝天的景象。對我們廣大gopher來說總歸是好事。
泛型很有可能會顛覆我們之前的很多設計,帶着這種疑問和衝動,我準備嘗試用golang泛型實現幾個orm的常見功能。
本文並沒完全實現通用的orm,只是探討其實現的一種方式提供各位讀者做借鑒。
雖然golang有了泛型,但是目前在標準庫sql底層還沒有改造,目前還有很多地方需要用到reflect。
調用方式
這個部分跟傳統的orm使用上沒有太大區別,沒辦法不使用反射的情況下,泛型的方式可能變得有點繁瑣。
調用方式
和創建table類似,寫入數據好像比沒有之前的orm有優勢。
讀取數據是非常高頻的操作,所以我們稍作封裝。
調用方式
稍微比原先的orm方式有了多一點想像空間,比如 在[T any]做更明確的約束,比如要求實現Filter定製方法。
鑒於本人能力還認證有限,目前還沒有發現泛型對orm劇烈的改進和突破的可能。未來如果go對底層sql做出改動,或者實現諸如Rust那種Enum方式,可能會帶來更多的驚喜。
大家知道為什麼golang不支持泛型
Golang團隊認為在類型系統和運行時的複雜性花費太大,還沒找到可以和這個複雜性相抵的良好設計。內置的map和slice其實都有泛型的味道,加上可以用interface{}來構造容器,可以達到泛型的效果。所以目前為止還沒有直接的支持泛型。
golang泛型前瞻
9月更新了 golang設計草稿中的泛型語法
這裡可以在線編譯
如下
這是一個泛型的Print函數 循環打印泛型切片中的元素
any是一種特殊的interface 在我的理解中 any可以看做包含基本類型在內的所有類型
除any外可以聲明任意一個包括多種基本類型的interface 如
有了Ordered可以實現一個比較函數:
當Gte的參數在Ordered的類型列表中時編譯可以通過
當Ordered的類型列表中的類型不支持=運算符時 編譯失敗
一個map函數的例子
對切片s中的每一個元素執行f 返回一個結果集
如何看待go語言泛型的最新設計?
Go 由於不支持泛型而臭名昭著,但最近,泛型已接近成為現實。Go 團隊實施了一個看起來比較穩定的設計草案,並且正以源到源翻譯器原型的形式獲得關注。本文講述的是泛型的最新設計,以及如何自己嘗試泛型。
例子
FIFO Stack
假設你要創建一個先進先出堆棧。沒有泛型,你可能會這樣實現:
type Stack []interface{}func (s Stack) Peek() interface{} {
return s[len(s)-1]
}
func (s *Stack) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack) Push(value interface{}) {
*s =
append(*s, value)
}
但是,這裡存在一個問題:每當你 Peek 項時,都必須使用類型斷言將其從 interface{} 轉換為你需要的類型。如果你的堆棧是 *MyObject 的堆棧,則意味着很多 s.Peek().(*MyObject)這樣的代碼。這不僅讓人眼花繚亂,而且還可能引發錯誤。比如忘記 * 怎麼辦?或者如果您輸入錯誤的類型怎麼辦?s.Push(MyObject{})` 可以順利編譯,而且你可能不會發現到自己的錯誤,直到它影響到你的整個服務為止。
通常,使用 interface{} 是相對危險的。使用更多受限制的類型總是更安全,因為可以在編譯時而不是運行時發現問題。
泛型通過允許類型具有類型參數來解決此問題:
type Stack(type T) []Tfunc (s Stack(T)) Peek() T {
return s[len(s)-1]
}
func (s *Stack(T)) Pop() {
*s = (*s)[:
len(*s)-1]
}
func (s *Stack(T)) Push(value T) {
*s =
append(*s, value)
}
這會向 Stack 添加一個類型參數,從而完全不需要 interface{}。現在,當你使用 Peek() 時,返回的值已經是原始類型,並且沒有機會返回錯誤的值類型。這種方式更安全,更容易使用。(譯註:就是看起來更醜陋,^-^)
此外,泛型代碼通常更易於編譯器優化,從而獲得更好的性能(以二進制大小為代價)。如果我們對上面的非泛型代碼和泛型代碼進行基準測試,我們可以看到區別:
type MyObject struct {
X
int
}
var sink MyObjectfunc BenchmarkGo1(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek().(MyObject)
}
}
func BenchmarkGo2(b *testing.B) {
for i := 0; i b.N; i++ {
var s Stack(MyObject)
s.Push(MyObject{})
s.Push(MyObject{})
s.Pop()
sink = s.Peek()
}
}
結果:
BenchmarkGo1BenchmarkGo1-16 12837528 87.0 ns/op 48 B/op 2 allocs/opBenchmarkGo2BenchmarkGo2-16 28406479 41.9 ns/op 24 B/op 2 allocs/op
在這種情況下,我們分配更少的內存,同時泛型的速度是非泛型的兩倍。
合約(Contracts)
上面的堆棧示例適用於任何類型。但是,在許多情況下,你需要編寫僅適用於具有某些特徵的類型的代碼。例如,你可能希望堆棧要求類型實現 String() 函數
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/287227.html