Go 新語法挺醜?最新的泛型類型約束介紹

大家好,我是煎魚。

近期我們在分享《3 件與 Go 開發者有關的大小事》時,裏面有一部分有提到 Go 泛型的約束類型語法又又又變了。

在評論區裏看到不少的讀者朋友大呼泛型的新類型約束語法挺醜,不如原本的好...

如下:

爲此,今天煎魚就帶大家來看看,爲什麼會發生泛型的新語法會這種改變?

問題背景

原本 @Ian Lance Taylor 設計的的泛型類型關鍵字如下:

type T interface {
 type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128, string
}

看起來好像非常 “順眼”。但在《proposal: Go 2: sum types using interface type lists[1]》中社區進行了熱烈的討論。

認爲該類型約束的關鍵字,過於 “模棱兩可”。像是 @Damien Neil 所提出的以下兩個例子。

結構體的例子:

package p
type mustIncludeDefaultCase struct{}
type MySum interface {
  type int, float64, mustIncludeDefaultCase
}

不明確的點之一,如果類型列表包含一個未導出的類型,那又應該是如何處理呢?

接口的例子:

type T interface { type int16, int32 }
func main() {
  var x T
  switch x.(type) {
  case int16:
  case int32:
  } 
}

你認爲程序會跑進哪個 switch-case 的代碼塊裏呢,是 int16,還是 int32?

不,都不會,變量 x 是 nil,如此迷惑。

在社區討論中,發現設計與真實場景一結合,發現這個類型規則在普通的接口類型、在約束中使用也太微妙了。

用類型列表嵌入接口時的行爲也很奇怪。認爲可以做的更好,那就是 “更顯式”。

新提案

爲此,Go 泛型的設計者 @Ian Lance Taylor 提出了一個新的提案《spec: generics: use type sets to remove type keyword in constraints[2]》。

其包含三個新的、更簡單的想法來取代泛型提案中定義的類型列表。

關鍵名詞

新語法在泛型處增加一個新概念:接口元素(interface elements),用作約束條件的接口類型,或者被嵌入約束條件的接口類型,允許嵌入一些額外的構造。

被嵌入的可以是:

重點名詞,我們繼續展開講解,分別是:

聯合元素

原先的語法中,類型約束會用逗號分隔的方式來展示。

如下:

type int, int8, int16, int32, int64

在新語法中,結合定義爲 union element(聯合元素),寫成一系列由豎線 ”|“ 分隔的類型或近似元素。

如下:

type PredeclaredSignedInteger interface {
 int | int8 | int16 | int32 | int64
}

常常會和下面講到的近似元素一起使用。

近似元素

新語法,他的標識符是 “~”,完整用法是 ~T~T 是指底層類型爲 T 的所有類型的集合。例如:

type AnyInt interface{ ~int }

他的類型集是 ~int,也就是所有類型爲 int 的類型(如:int、int8、int16、int32、int64)都能夠滿足這個類型約束的條件。

再結合以上的分隔來使用,用法爲:

type SignedInteger interface {
 ~int | ~int8 | ~int16 | ~int32 | ~int64
}

相當於泛型提案中使用的以下類型:

interface {
 type int, int8, int16, int32, int64
}

新語法只需藉助近似標識符 ~int 來表達就可以了,更明確的表示了近似匹配,而不是存在隱式理解。

嵌入約束

一個類型約束可以嵌入另一個約束,聯合元素可以包括約束。

例如:

// Signed is a constraint whose type set is any signed integer type.
type Signed interface {
 ~int | ~int8 | ~int16 | ~int32 | ~int64
}

// Unsigned is a constraint whose type set is any unsigned integer type.
type Unsigned interface {
 ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

// Float is a constraint whose type set is any floating point type.
type Float interface {
 ~float32 | ~float64
}

// Ordered is a constraint whose type set is any ordered type.
// That is, any type that supports the < operator.
type Ordered interface {
 Signed | Unsigned | Float | ~string
}

這個很好理解,就是正式支持嵌入約束了。

接口類型(聯合約束元素)

在聯合元素中,使用接口類型的話。將會把該類型集添加到聯合中。

例如:

type Stringish interface {
 string | fmt.Stringer
}

Stringish 的類型集將是字符串類型和所有實現 fmt.Stringer 的類型,任何這些類型(包括 fmt.Stringer 本身)將被允許作爲這個約束的類型參數。

也就是針對接口類型做了特殊的處理。

總結

今天這篇文章,主要講解了 Go 泛型的新語法,其實本質上還是爲了解決引入 A 後,出現了 BCD 新問題,又繼續引入新的語法和模式來解決。

整體就是三個觀點:

這就是用這兩個新語法符號的原因,被嫌醜的新語法標識符 “|” 和 “~” ,其實也是在 issues 的大範圍討論中,由社區貢獻出來的。

算是有利有弊?你的看法如何,歡迎在評論區留言:)

參考資料

[1]

proposal: Go 2: sum types using interface type lists: https://github.com/golang/go/issues/41716

[2]

spec: generics: use type sets to remove type keyword in constraints: https://github.com/golang/go/issues/45346

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/A1JYIbPmjIpCOg2TAzdcnA