初探 golang 1.18 generics 功能

logo

Go 語言在近期內會推出 1.18 版本,而 1.18 最重要的功能之一就是 generics,在此版本之前,最令人詬病的就是 Go 無法支援多種 Type 當參數,造成在寫 Package 時候多出很多重複性的程式碼,本篇會教大家基礎認識什麼是 generics,及怎麼使用。

影片教學

影片視頻會同步放到底下課程內

安裝 1.18 及整合 Vscode 編輯器

首先要先安裝 go1.18 beta2 版本,方式很簡單,請參考底下

1
2
go install golang.org/dl/go1.18beta2@latest
go1.18beta2 download

完成後可以透過底下指令看到 go1.18beta2

1
2
$ go1.18beta2 env GOROOT
/Users/mtk10671/sdk/go1.18beta2

如果要快速嘗試用 go 指令,可以直接透過 alias 指令

1
alias go=go1.18beta2

該如何整合到 Vscode 內呢?請參考這篇文章,幾個步驟就可以完成。第一個步驟就是請把 go1.18beta2 變成環境變數預設值 PATH,請改成

1
PATH="$HOME/sdk/go1.18beta2/bin:$PATH"

打開 VSCODE 編輯器,重新安裝全部 tools,直接 cmd + shift + p 選擇 Go: Install/Update Tools,把全部 toll 打勾重新安裝。最後步驟就是打開預設的 config.json 檔案加上

1
2
3
"gopls": {
  "ui.semanticTokens": true
}

使用 generic

我們來寫一個 func 支援一個參數,此參數可以是 int64float64 型態,線上執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

func show[num int64 | float64](s num) {
  fmt.Println(s)
}

func main() {
  fmt.Println("go 1.18 Generics Example")

  var sum1 int64 = 28
  var sum2 float64 = 29.5

  show(sum1)
  show(sum2)
}

大家可以看到 [num int64 | float64] 定義一個新的型態,此型態可以為 int64float64,再把這型態放到後面。不過如果支援型態很多的話,可以改寫如下,線上執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type age interface {
  int8 | int16 | int32 | int64 | float32 | float64
}

func show[num age](s1 num) {
  val := float64(s1) + 1
  fmt.Println(val)
}

func main() {
  fmt.Println("go 1.18 Generics Example")

  var sum1 int64 = 28
  var sum2 float64 = 29.5

  show(sum1)
  show(sum2)
}

在程式碼內定義一個新的 interface 將所有型態放入即可

1
2
3
type age interface {
  int8 | int16 | int32 | int64 | float32 | float64
}

如果要支援多個參數可以怎麼寫

1
2
3
4
func total[num age](s1, s2 num) {
  val := float64(s1) + float64(s2)
  fmt.Println(val)
}

這樣就可以同時帶入兩個一樣的參數型態。如果要帶入不同的型態,第一個參數是 int64,第二個是 float64,請改成如下

1
2
3
4
func summary[num1, num2 age](s1 num1, s2 num2) {
  val := float64(s1) + float64(s2)
  fmt.Println(val)
}

完整測試程式碼可以參考底下,線上執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import "fmt"

type age interface {
  int8 | int16 | int32 | int64 | float32 | float64
}

func newGenerics[num age](s1 num) {
  val := float64(s1) + 1
  fmt.Println(val)
}

func total[num age](s1, s2 num) {
  val := float64(s1) + float64(s2)
  fmt.Println(val)
}

func summary[num1, num2 age](s1 num1, s2 num2) {
  val := float64(s1) + float64(s2)
  fmt.Println(val)
}

func main() {
  fmt.Println("go 1.18 Generics Example")

  var sum1 int64 = 28
  var sum2 float64 = 29.5

  newGenerics(sum1)
  newGenerics(sum2)

  var sum3 float64 = 28
  var sum4 float64 = 29.5

  total(sum3, sum4)

  var sum5 int64 = 28
  var sum6 float64 = 29.5

  summary(sum5, sum6)
}

最後我們來看一個例子泡沫排序 (Bubble sort)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

// conver to generics type to support int and float64 types
func bubbleSort(array []int) []int {
  for i := 0; i < len(array)-1; i++ {
    for j := 0; j < len(array)-i-1; j++ {
      if array[j] > array[j+1] {
        array[j], array[j+1] = array[j+1], array[j]
      }
    }
  }
  return array
}

func main() {
  array := []int{11, 14, 3, 8, 18, 17, 43}
  fmt.Println(bubbleSort(array))
}

除了支援 int 之外,也請支援 float64。請直接看底下解答,線上執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

// The Time Complexity of the Bubble Sort is O(n^2) since it takes two nested loops to check the adjacent element.
// For example, let’s take the following unsorted array −
// 22 15 11 45 13
// Bubble Sort Algorithm first traverses the whole array and then in another loop checks if the adjacent elements are in order or not.
// Thus, after sorting the elements will be,
// 11 13 15 22 45

// conver to generics type to support both int and float64 types

type Number interface {
  int | int32 | int64 | float32 | float64
}

func bubbleSort[n Number](array []n) []n {
  for i := 0; i < len(array)-1; i++ {
    for j := 0; j < len(array)-i-1; j++ {
      if array[j] > array[j+1] {
        array[j], array[j+1] = array[j+1], array[j]
      }
    }
  }
  return array
}

func main() {
  n1 := []int{11, 14, 3, 8, 18, 17, 43}
  fmt.Println(bubbleSort(n1))
  n2 := []float64{11.1, 14.2, 3.3, 8.4, 18.5, 17.6, 43.7}
  fmt.Println(bubbleSort(n2))
}

心得

終於有機會可以優化一些 Package 的寫法,不過為了向下相容,原本的 go1.17 寫法還是會保留,透過 go build tag 方式來決定不同的使用環境即可。


See also