Go 泛型

guyan0319 發表於 2022-11-24

在最新的go1.18版中增加了期盼已久的泛型支援

什麼是泛型

泛型是程式設計語言的一種風格或正規化。泛型允許程式設計師在強型別程式設計語言中編寫程式碼時使用一些以後才指定的型別,在例項化時作為引數指明這些型別。

為什麼使用泛型

如果沒有泛型,對於golang語言這種強型別語言,針對不同型別的函式解決方式:

  • 每個型別分別實現一遍,程式碼冗長,閱讀性差。
  • 透過interface{},需要反射型別判斷及型別強轉,這容易暴露錯誤。

以int64和int型別為例:

func CompareInt64(a, b int64) bool {
    if a >= b {
        return true
    }
    return false
}
func CompareInt(a, b int) bool {
    if a >= b {
        return true
    }
    return false
}
func Compare(a, b interface{}) bool {
    switch a.(type) {
    case int64:
        a1 := a.(int64)
        b1 := b.(int64)
        if a1 >= b1 {
            return true
        }
        return false
    case int:
        a1 := a.(int)
        b1 := b.(int)
        if a1 >= b1 {
            return true
        } 
        return false
    default:
        return false
    }
}

使用泛型
golang支援泛型函式和泛型型別

// 泛型函式
func F[T any](p T) (args T){ ... }

[T any]為型別約束,any 表示任意型別,(args T)為引數。

使用泛型實現上例

func Compare[T int64|int](a, b T) bool {
    if a >= b {
        return true
    }
    return false
}

引數多的話也可這樣

type TT interface {
    int | int64
}
func Compare[T TT|int](a, b T) bool {
    if a >= b {
        return true
    }
    return false
}

有了泛型後:

  • 編譯期間確定型別,保證型別安全
  • 提升可讀性,從編碼階段就顯式地知道泛型集合、泛型方法等
  • 泛型合併了同型別的處理程式碼提高程式碼的重用率,增加程式的通用靈活性。

    泛型使用示例

    泛型切片

    預宣告識別符號 any是空介面的別名。它可以代替 interface{}

package main

import (
    "fmt"
)

// 泛型切片
type Name[T any] []T
type TestSilce [T int | float64] []T

func ListElem[T int  | float64 | string](params []T)  {
    for _, elem := range params {
        fmt.Printf("型別=%T,val=%+v\n", elem, elem)
    }
    return
}

func main() {
    v := Name[string]{"a", "b"}
    fmt.Printf("型別=%T,val=%+v\n", v, v)
    ListElem(v)
    l := TestSilce[int]{1, 2, 3}
    fmt.Printf("型別=%T,val=%+v\n", l, l)
    ListElem(l)
}

泛型map

預宣告識別符號 comparable是一個介面,表示可以使用==or進行比較的所有型別的集合!=。它只能用作(或嵌入)型別約束。

package main

import "fmt"

type Number interface {
    int64 | float64
}
type M[K string ,V any] map[K]V

func main() {
    // Initialize a map for the integer values
    ints := M[string, int64]{
        "first": 34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[string]float64{
        "first": 35.98,
        "second": 26.99,
    }

    fmt.Printf("Generic Sums: %v and %v\n",
        SumIntsOrFloats[string, int64](ints),
        SumIntsOrFloats[string, float64](floats))

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))

    fmt.Printf("Generic Sums with Constraint: %v and %v\n",
        SumNumbers(ints),
        SumNumbers(floats))
}
// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}

// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

泛型結構體

package main

import "fmt"

type Number interface {
    int64 | float64
}
type Stack[V Number] struct {
    size int64
    value []V
}

func (s *Stack[V]) Push(v V) {
    s.value = append(s.value, v)
    s.size++
}

func main() {
    s := &Stack[int64]{}
    s.Push(1)
    fmt.Printf(" %T and %v\n",s,s)
}

泛型通道

package main

import "fmt"
type Ch[T any] chan T
func main() {
    ch := make(Ch[int], 1)
    ch <- 10

    res := <-ch
    fmt.Printf("型別=%T,val=%+v", res, res)
}

當前的泛型實現具有以下已知限制:

  • Go 編譯器無法處理泛型函式或方法中的型別宣告。我們希望在未來的版本中提供對此功能的支援。
  • Go 編譯器不接受具有預宣告函式 real、imag 和 complex 的型別引數型別的引數。 我們希望在未來的版本中取消此限制。
  • 如果 m 由 P 的約束介面顯式宣告,則 Go 編譯器僅支援在型別引數型別為 P 的值 x 上呼叫方法 m。 類似地,方法值 x.m 和方法表示式 P.m 也僅在 m 由 P 顯式宣告時才受支援,即使 m 可能由於 P 中的所有型別都實現 m 而位於 P 的方法集中。 我們希望在未來的版本中取消此限制。。
  • Go 編譯器不支援訪問結構欄位 x.f,其中 x 是型別引數型別,即使型別引數的型別集中的所有型別都有欄位 f。 我們可能會在未來的版本中刪除此限制。
  • 不允許將型別引數或指向型別引數的指標作為結構型別中的未命名欄位嵌入。 同樣,不允許在介面型別中嵌入型別引數。 目前尚不清楚這些是否會被允許。
  • 具有多個術語的聯合元素可能不包含具有非空方法集的介面型別。 目前尚不清楚這是否會被允許。

總結

函式和 型別宣告 的語法 現在接受 型別引數。
引數化函式和型別可以透過在它們後面加上方括號中的型別引數列表來例項化。
新標記~已新增到 運算子和標點符號集中。
預宣告識別符號 any是空介面的別名。它可以代替 interface{}.
介面型別 的語法 現在允許嵌入任意型別(不僅僅是介面的型別名稱)以及聯合和~T型別元素。此類介面只能用作型別約束。一個介面現在定義了一組型別和一組方法。
預宣告識別符號 comparable是一個介面,表示可以使用==or進行比較的所有型別的集合!=。它只能用作(或嵌入)型別約束。
加入泛型後對編譯效能有影響,編譯速度慢了15%。

links

https://go.dev/doc/tutorial/g...
https://blog.csdn.net/Jcduhdt...