<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          跟著 Go 作者掌握泛型

          共 686字,需瀏覽 2分鐘

           ·

          2022-02-26 15:30

          閱讀本文大概需要 5 分鐘。

          大家好,我是 polarisxu。

          在 GopherCon 2021 年大會上,Go 兩位作者 Robert Griesemer 和 Ian Lance Taylor 做了泛型相關(guān)的演講,即將在 Go1.18 發(fā)布的 Go 泛型,正是兩位設(shè)計的。一直想著把他們的演講做一個梳理,然后分享給大家。拖的有點久,趁春節(jié)假期整理出來了。

          注意,本文中的 constraints 包,已經(jīng)確定在 Go1.18 正式版中去除,放入 golang.org/x/exp 中。詳細可以參考該文:Go1.18 這個包確定沒了

          Go1.18 關(guān)于泛型部分,主要有三個特性:

          • Type parameters for functions and types,即函數(shù)和類型的類型參數(shù)
          • Type sets defined by interfaces,即由接口定義的類型集合
          • Type inference,即類型推斷

          1、類型參數(shù)

          先看函數(shù)的類型參數(shù)。

          類型參數(shù)列表(Type parameter lists)

          類型參數(shù)列表看起來是帶方括號的普通參數(shù)列表。通常,類型參數(shù)以大寫字母開頭,以強調(diào)它們是類型:

          [P,?Q?constraint1,?R?constraint2]

          看一個例子。

          非泛型版本的求最小值:

          func?min(x,?y?float64)?float64?{
          ??if?x?????return?x
          ??}
          ??return?y
          }

          如果有 int 類型的 min 版本需求,得另外寫一個類似的函數(shù),這完全是重復(fù)代碼。

          那泛型版本呢?

          func?min[T?constraints.Ordered](x,?y?T)?T?{
          ??if?x?????return?x
          ??}
          ??return?y
          }

          注意和上面版本的區(qū)別。

          • 多了一個 [T constraints.Ordered],這就是類型參數(shù)列表,聲明了一個類型 T,它的約束是 constraints.Ordered,即類型 T 滿足它規(guī)定的條件
          • 參數(shù)類型 float64 變成了 T,而不是具體的某個類型

          那這個泛型函數(shù)如何調(diào)用呢?

          m?:=?min[int](2,?3)

          是不是很奇怪?其實仔細一琢磨,好像沒問題。因為函數(shù)聲明中有 ?[T constraints.Ordered],跟普通的函數(shù)參數(shù)有點像。調(diào)用時,提供 int,表明普通函數(shù)參數(shù)是 int 類型。

          實例化

          在調(diào)用時,會進行實例化過程:

          1)用類型實參(type arguments)替換類型形參(type parameters)

          2)檢查類型實參(type arguments)是否實現(xiàn)了類型約束

          如果第 2 步失敗,實例化(調(diào)用)失敗。

          所以,調(diào)用過程可以分解為以下兩步:

          fmin?:=?min[float64]
          m?:=?fmin(2.3,?3.4)

          //?和下面等價
          m?:=?min[float64](2.3,?3.4)
          //?相當(dāng)于?m?:=?(min[float64])(2.3,?3.4)

          所以,實例化產(chǎn)生了一個非泛型函數(shù)。

          類型的類型參數(shù)

          類型也可以有類型參數(shù)。通過一個例子理解一下。

          一個泛型版二叉樹:

          type?Tree[T?interface{}]?struct?{
          ?left,?right?*Tree[T]
          ?data????????T
          }

          func?(t?*Tree[T])?Lookup(x?T)?*Tree[T]

          var?stringTree?Tree[string]

          注意其中的 [T interface{}] ,跟函數(shù)的類型參數(shù)語法是一樣的,T 相當(dāng)于是一個類型,所以,之后用到 Tree 的地方,T 都跟隨著,即 Tree[T],包括方法的接收者(receiver)。

          注意實例化的地方:var stringTree Tree[string],和上面兩個實例化步驟中的第一步一樣。

          2、類型集合(Type sets)

          先看值參數(shù)的類型(the type of value parameters)。

          函數(shù)普通參數(shù)列表中的每個值參數(shù)都有一個類型,這個類型定義值的集合。比如 float64 定義了浮點數(shù)值的集合。

          相應(yīng)的有類型參數(shù)的類型(the type of type parameters),也就是說,類型參數(shù)列表中的每個類型參數(shù)都有一個類型,這個類型定義了類型的集合,這叫做類型約束(type constraint):

          func?min[T?constraints.Ordered](x,?y?T)?T

          這里的 constraints.Ordered 是類型參數(shù)列表中的 T 參數(shù)的類型,它定義了類型的集合,即類型約束。

          constraints.Ordered 是 Go1.18 內(nèi)置的一個類型約束,它有兩個功能:

          • 只有值支持排序的類型才能傳遞給類型參數(shù) T;
          • T 類型的值必須支持 < 操作符,因為函數(shù)體中使用了該操作符。

          類型約束是接口

          大家都知道接口定義了方法集(method sets),演講中給了一張圖:

          method sets

          根據(jù) Go 的規(guī)則,類型 P、Q、R 方法中包含了 a、b、c,因此它們實現(xiàn)了接口。

          所以,反過來可以說,接口也定義了類型集(type sets):

          type sets

          上圖中,類型 P、Q、R 都實現(xiàn)了左邊的接口(因為都實現(xiàn)了接口的方法集),因此我們可以說該接口定義了類型集。

          既然接口是定義類型集,只不過是間接定義的:類型實現(xiàn)接口的方法集。而類型約束是類型集,因此完全可以重用接口的語義,只不過這次是直接定義類型集:

          interface defines type sets

          這就是類型約束的語法,通過接口直接定義類型集:

          type?Xxx?interface?{
          ??int?|?string?|?bool
          }

          而 constraints.Ordered 的定義如下:

          //?Ordered?is?a?constraint?that?permits?any?ordered?type:?any?type
          //?that?supports?the?operators?=?>.
          //?If?future?releases?of?Go?add?new?ordered?types,
          //?this?constraint?will?be?modified?to?include?them.
          type?Ordered?interface?{
          ?Integer?|?Float?|?~string
          }

          Ordered 定義了所有 interger、浮點數(shù)和字符串類型的集合。所以,< 操作符也是支持的。這其中的 Integer、Float 也在 constraints 包有定義。

          細心的朋友應(yīng)該發(fā)現(xiàn)了 ~string,類型前面的 ~~T 意味著包含底層類型 T 的所有類型集合。

          如果約束中的所有類型都支持一個操作,則該操作可以與相應(yīng)的類型參數(shù)一起使用

          除了將約束單獨定義為類型外,還可以寫成字面值的形式,比如:

          [S?interface{~[]E},?E?interface{}]

          這看著有點暈,其實可以直接這么寫:

          [S?~[]E,?E?interface{}]

          Go1.18 中,any 是 interface{} 的別名,因此可以進一步寫為:

          [S?~[]E,?E?any]

          E 是切片的元素類型,~[]E 表示底層是 []E 切片類型的都符合該約束。

          3、類型推斷

          在調(diào)用泛型函數(shù)時,提供類型實參感覺有點多余。Go 雖然是靜態(tài)類型語言,但擅長類型推斷。因此泛型這里,Go 也實現(xiàn)了類型推斷。

          調(diào)用泛型版的 min,可以不提供類型實參,而是直接由 Go 進行類型推斷:

          var?a,?b,?m?float64
          m?:=?min[float64](a,?b)

          類型推斷的細節(jié)很復(fù)雜,但使用起來還是很簡單,大部分時候,跟普通函數(shù)調(diào)用沒有區(qū)別。

          關(guān)于類型推斷,演講中給了一個例子:

          func?Scale[E?constraints.Integer](s?[]E,?c?E)?[]E?{
          ?r?:=?make([]E,?len(s))
          ?for?i,?v?:=?range?s?{
          ??r[i]?=?v?*?c
          ?}
          ?return?r
          }

          這個函數(shù)的目的是希望對 s 中的每個元素都乘以參數(shù) c,最后返回一個新的切片。

          接著定義一個類型:

          type?Point?[]int32

          func?(p?Point)?String()?string?{
          ?//?實現(xiàn)細節(jié)不重要,忽略
          ?return?"point"
          }

          很顯然,Point 類型的切片可以傳遞給 Scale:

          func?ScaleAndPrint(p?Point)?{
          ?r?:=?Scale(p,?2)
          ?fmt.Println(r.String())
          }

          我們希望對 p 進行 Scale,得到一個新的 p,但發(fā)現(xiàn)返回的 r 根本不是 Point:

          func?main()?{
          ?p?:=?Point{3,?2,?4}
          ?ScaleAndPrint(p)
          }

          會報錯:r.String undefined (type []int32 has no field or method String)

          所以,我們應(yīng)該這樣修改 Scale 函數(shù):

          func?Scale[S?~[]E,?E?constraints.Integer](s?S,?c?E)?S?{
          ?r?:=?make(S,?len(s))
          ?for?i,?v?:=?range?s?{
          ??r[i]?=?v?*?c
          ?}
          ?return?r
          }

          注意其中的變化:加入了泛型 S,以及額外的類型約束 ~[]E

          調(diào)用 Scale 時,不需要 r := Scale[Point, int32](p, 2),因為 Go 會進行類型推斷。

          正確的完整代碼如下:

          package?main

          import?(
          ?"constraints"
          ?"fmt"
          )

          func?Scale[S?~[]E,?E?constraints.Integer](s?S,?c?E)?S?{
          ?r?:=?make(S,?len(s))
          ?for?i,?v?:=?range?s?{
          ??r[i]?=?v?*?c
          ?}
          ?return?r
          }

          type?Point?[]int32

          func?(p?Point)?String()?string?{
          ?//?實現(xiàn)細節(jié)不重要,忽略
          ?return?"point"
          }

          func?ScaleAndPrint(p?Point)?{
          ?r?:=?Scale(p,?2)
          ?fmt.Println(r.String())
          }

          func?main()?{
          ?p?:=?Point{3,?2,?4}
          ?ScaleAndPrint(p)
          }

          4、什么時候用泛型

          泛型的加入,無疑增加了復(fù)雜度。我個人認為,能不用泛型就不用泛型。在演講中,兩位大佬提到,在以下場景可以考慮使用泛型:

          • 對于 slice、map、channel 等類型,如果它們的元素類型是不確定的,操作這類類型的函數(shù)可以考慮用泛型
          • 一些通用目的的數(shù)據(jù)結(jié)構(gòu),比如前面提到的二叉樹等
          • 如果一些函數(shù)行為相同,只是類型不同,可以考慮用泛型重構(gòu)

          注意,目前 Go 方法不支持類型參數(shù),所以,如果方法有需要泛型的場景,可以轉(zhuǎn)為函數(shù)的形式。

          此外,不要為了泛型而泛型。比如這樣的泛型就很糟糕:

          func?ReadFour[T?io.Reader](r?T)?([]byte,?error)

          而應(yīng)該使用非泛型版本:

          func?ReadFour(r?io.Reader)?([]byte,?error)

          5、總結(jié)

          泛型是一把雙刃劍。泛型的加入,讓 Go 不那么簡單了。有些代碼寫出來,可讀性可能非常差。我們應(yīng)該按沒有泛型時候?qū)懘a,當(dāng)發(fā)現(xiàn)在 Repeat Yourself 時,再考慮能不能用泛型重構(gòu),千萬別玩什么花樣!

          最后,放上演講的視頻地址,有興趣的可以觀看:https://www.youtube.com/watch?v=Pa_e9EeCdy8。




          往期推薦


          我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標準庫》等。


          堅持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio

          瀏覽 57
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色干逼视频 | 无码在线观看一区二区三区 | 日本a在线观看 | 乱论理论激情亚州欧美 | av在线一区二区 av在线资源观看 |