Golang 泛型設(shè)計(jì)草案

原文地址:https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md

摘要

我們建議擴(kuò)展Go語(yǔ)言,以便為類型和函數(shù)添加可選的類型參數(shù)。類型參數(shù)可能受接口類型的約束膀斋。我們還建議擴(kuò)展用作類型約束的接口類型,允許列出可能分配給它們的類型集湘换。在許多情況下,可以支持通過(guò)統(tǒng)一算法進(jìn)行類型推斷统阿,從而可以從函數(shù)調(diào)用中省略類型實(shí)參彩倚。該設(shè)計(jì)與Go 1 完全向后兼容。

如何閱讀本文

這篇文章很長(zhǎng)扶平。下面是一些有關(guān)如何閱讀的指南帆离。

  • 我們從一個(gè)高層次的概述開始,非常簡(jiǎn)要地描述這些概念蜻直。
  • 然后盯质,我們從頭開始解釋整個(gè)設(shè)計(jì)袁串,并通過(guò)簡(jiǎn)單的示例介紹我們需要的細(xì)節(jié)概而。
  • 完整描述設(shè)計(jì)之后呼巷,我們將討論實(shí)現(xiàn),設(shè)計(jì)中的一些問(wèn)題以及與其他泛型方法的比較赎瑰。
  • 然后王悍,我們提供有關(guān)如何在實(shí)踐中使用此設(shè)計(jì)的幾個(gè)完整示例。
  • 在示例之后餐曼,附錄中討論了一些次要細(xì)節(jié)压储。

高度概括

本節(jié)非常簡(jiǎn)短地解釋了設(shè)計(jì)草案建議的更改。本部分適用于已經(jīng)熟悉泛型如何在Go語(yǔ)言中工作的人們源譬。這些概念將在后續(xù)各節(jié)中詳細(xì)說(shuō)明集惋。

  • 函數(shù)可以使用關(guān)鍵字type引入的其他類型參數(shù)列表:func F(type T)(p T) { ... }
  • 這些類型參數(shù)可以作為常規(guī)參數(shù)在函數(shù)體內(nèi)使用踩娘。
  • 類型也可以具有類型參數(shù)列表:type M(type T) []T刮刑。
  • 每個(gè)類型參數(shù)可以具有可選的類型約束: func F(type T Constraint)(p T) { ... }
  • 類型約束是接口類型。
  • 用作類型約束的接口類型可以擁有預(yù)先聲明的類型的列表养渴。只有其基礎(chǔ)類型是這些類型之一的類型才能實(shí)現(xiàn)該接口雷绢。
  • 使用泛型函數(shù)或類型需要傳遞類型實(shí)參。
  • 在常見情況下理卑,可以通過(guò)類型推斷省略類型實(shí)參翘紊。
  • 如果類型參數(shù)具有類型約束,則其類型實(shí)參必須實(shí)現(xiàn)此接口藐唠。
  • 泛型函數(shù)只能使用類型約束所允許的操作帆疟。

在以下各節(jié)中,我們將詳細(xì)介紹每種更改宇立。你也可以跳過(guò)這些直接查看示例踪宠。

背景

這個(gè)版本的設(shè)計(jì)草案與2019年7月31日提出的版本有很多相似之處,但協(xié)議已被刪除并由接口類型代替泄伪。

有許多要求為Go添加泛型支持的請(qǐng)求殴蓬。在issue在線文檔中已經(jīng)進(jìn)行了廣泛的討論。

有幾種添加類型參數(shù)的建議蟋滴,可以通過(guò)上面的鏈接找到染厅。這里提出的許多想法以前都曾出現(xiàn)過(guò)。此處描述的主要新特性是語(yǔ)法和對(duì)作為約束的接口類型的仔細(xì)校準(zhǔn)津函。

此設(shè)計(jì)草案建議擴(kuò)展Go語(yǔ)言肖粮,添加一種參數(shù)多態(tài)的形式,其中類型參數(shù)不受聲明的子類型關(guān)系(如在某些面向?qū)ο蟮恼Z(yǔ)言中)的約束尔苦,但受顯式定義的約束結(jié)構(gòu)的約束涩馆。

此設(shè)計(jì)不支持模板編程或任何其他形式的編譯時(shí)處理行施。

由于術(shù)語(yǔ)“泛型”在Go社區(qū)中得到了廣泛使用,因此我們將在下文中將其用作表示具有類型參數(shù)的函數(shù)或類型的簡(jiǎn)寫魂那。不要將本設(shè)計(jì)中使用的泛型(generic)一詞與其他語(yǔ)言(例如C ++蛾号,C#,Java或Rust)中的同一術(shù)語(yǔ)混淆涯雅;它們有相似之處鲜结,但不相同。

設(shè)計(jì)

我們將基于簡(jiǎn)單的示例分階段描述完整的設(shè)計(jì)活逆。

類型參數(shù)

泛型代碼是使用稍后將指定的類型編寫的代碼丁鹉。未指定的類型稱為類型參數(shù)鸟赫。運(yùn)行泛型代碼時(shí),type形參將設(shè)置為type實(shí)參

這是一個(gè)打印出切片的每個(gè)元素的函數(shù)匈庭,其中切片的元素類型(此處稱為T)是未知的剩失。這是我們要允許以支持泛型編程的函數(shù)類型的一個(gè)簡(jiǎn)單例子勘究。(稍后我們還將討論泛型類型)渗鬼。

// Print prints the elements of a slice.
// It should be possible to call this with any slice value.
func Print(s []T) { // Just an example, not the suggested syntax.
    for _, v := range s {
        fmt.Println(v)
    }
}     

使用這種方法,首先要做出的決定是:如何聲明T類型參數(shù)迷殿?在Go之類的語(yǔ)言中儿礼,我們希望每個(gè)標(biāo)識(shí)符都以某種方式聲明。

在這里庆寺,我們進(jìn)行的設(shè)計(jì)決策是:類型參數(shù)與普通的非類型函數(shù)參數(shù)相似蚊夫,因此應(yīng)與其他參數(shù)一起列出。但是懦尝,類型參數(shù)與非類型參數(shù)不同知纷,因此,盡管它們出現(xiàn)在參數(shù)列表中陵霉,但我們還是要加以區(qū)分琅轧。因此我們做出以下設(shè)計(jì)決策:我們定義一個(gè)附加的,可選的參數(shù)列表踊挠,描述類型參數(shù)乍桂。該參數(shù)列表出現(xiàn)在常規(guī)參數(shù)之前。它以關(guān)鍵字type開頭效床,并列出類型參數(shù)睹酌。

// Print prints the elements of any slice.
// Print has a type parameter T, and has a single (non-type)
// parameter s which is a slice of that type parameter.
func Print(type T)(s []T) {
    // same as above
}

這表示在函數(shù)Print中,標(biāo)識(shí)符T是一個(gè)類型參數(shù)剩檀,一種當(dāng)前未知的類型憋沿,但在調(diào)用該函數(shù)時(shí)將是已知的。如上所述沪猴,當(dāng)描述普通的非類型參數(shù)時(shí)辐啄,類型參數(shù)可以用作類型采章。它也可以在函數(shù)體內(nèi)使用。

由于Print具有類型參數(shù)壶辜,因此任何調(diào)用都Print必須提供類型實(shí)參悯舟。稍后,我們將看到通常如何通過(guò)使用函數(shù)參數(shù)類型推斷來(lái)使用非類型參數(shù)推導(dǎo)出該類型參數(shù)∈扛矗現(xiàn)在图谷,我們將顯式傳遞type參數(shù)翩活。類型實(shí)參的傳遞與聲明類型形參的傳遞非常相似:作為單獨(dú)的參數(shù)列表阱洪。在調(diào)用時(shí)不使用type關(guān)鍵字。

    // Call Print with a []int.
    // Print has a type parameter T, and we want to pass a []int,
    // so we pass a type argument of int by writing Print(int).
    // The function Print(int) expects a []int as an argument.

    Print(int)([]int{1, 2, 3})

    // This will print:
    // 1
    // 2
    // 3

約束條件

讓我們的例子稍微復(fù)雜一些菠镇。讓我們將其轉(zhuǎn)換為一個(gè)函數(shù)冗荸,該函數(shù)可以將任意類型的切片轉(zhuǎn)換為[]string。

// This function is INVALID.
func Stringify(type T)(s []T) (ret []string) {
    for _, v := range s {
        ret = append(ret, v.String()) // INVALID
    }
    return ret
} 

乍一看似乎不錯(cuò)利耍,但在此示例v為type T蚌本,而我們對(duì)T一無(wú)所知。特別是隘梨,我們不知道T有String方法程癌。因此,v.String()的調(diào)用無(wú)效轴猎。

當(dāng)然嵌莉,在支持泛型編程的其他語(yǔ)言中也會(huì)出現(xiàn)相同的問(wèn)題。例如捻脖,在C ++中锐峭,泛型函數(shù)(用C ++術(shù)語(yǔ)來(lái)說(shuō)是函數(shù)模板)可以對(duì)泛型類型的值調(diào)用任何方法。也就是說(shuō)可婶,在C ++方法中沿癞,調(diào)用v.String()就可以了。如果使用沒(méi)有String方法的類型實(shí)參調(diào)用該函數(shù)矛渴,則在編譯v.String時(shí)將會(huì)報(bào)錯(cuò)椎扬。這些錯(cuò)誤可能很長(zhǎng),因?yàn)樵阱e(cuò)誤發(fā)生之前可能有幾層函數(shù)調(diào)用具温,為了了解出了什么問(wèn)題必須報(bào)告所有這些層的異常蚕涤。

對(duì)于Go語(yǔ)言,C ++方法將是一個(gè)糟糕的選擇桂躏。原因之一是語(yǔ)言的風(fēng)格钻趋。在Go語(yǔ)言中,我們不會(huì)進(jìn)行名稱的引用剂习,例如在這種情況下蛮位,是希望String存在较沪。Go會(huì)將所有看到的名稱解析為其聲明。

另一個(gè)原因是Go旨在支持大規(guī)模編程失仁。我們必須考慮以下情況:泛型函數(shù)定義(Stringify)和對(duì)泛型函數(shù)的調(diào)用(未顯示尸曼,但可能在其他軟件包中)相距甚遠(yuǎn)。通常萄焦,所有泛型代碼都希望類型實(shí)參滿足某些要求控轿。我們將這些要求稱為約束(其他語(yǔ)言也有類似的想法,稱為類型界限或特征界限或其他的概念)拂封。在這種情況下茬射,約束非常明顯:類型必須具有String() string方法。在其他情況下冒签,它可能不那么明顯在抛。

我們不想從Stringify的任何使用中得出約束(在這種情況下,調(diào)用String方法)萧恕。如果這樣做刚梭,對(duì)Stringify的微小更改可能會(huì)更改約束。這意味著較小的更改可能導(dǎo)致調(diào)用該函數(shù)的代碼變更而意外中斷票唆。Stringify故意更改其約束并強(qiáng)迫用戶進(jìn)行更改也是一種方法朴读。我們要避免的是意外更改Stringify約束。

這意味著約束必須對(duì)調(diào)用者傳遞的類型實(shí)參和泛型函數(shù)中的代碼都設(shè)置限制走趋。調(diào)用者只能傳遞滿足約束的類型參數(shù)衅金。泛型函數(shù)只能以約束所允許的方式使用這些值。我們認(rèn)為這是一條重要規(guī)則吆视,適用于在Go中定義泛型編程的任何嘗試:泛型代碼只能使用已知其類型參數(shù)實(shí)現(xiàn)的操作典挑。

允許任何類型的操作

在進(jìn)一步討論約束之前,讓我們簡(jiǎn)要地指出在沒(méi)有約束的情況下會(huì)發(fā)生什么啦吧。如果泛型函數(shù)沒(méi)有為類型參數(shù)指定約束(如上述Print方法那樣)您觉,則該參數(shù)允許使用任何類型參數(shù)。泛型函數(shù)與該類型參數(shù)的值一起使用的操作只能是允許用于任何類型的值的那些操作授滓。在上面的示例中琳水,Print函數(shù)聲明了一個(gè)變量v,類型為類型參數(shù)T般堆,并將該變量傳遞給函數(shù)在孝。

允許任何類型的操作是:

  • 聲明那些類型的變量
  • 將相同類型的其他值分配給這些變量
  • 將這些變量傳遞給函數(shù)或從函數(shù)返回它們
  • 獲取那些變量的地址
  • 將這些類型的值轉(zhuǎn)換或賦值給類型 interface{}
  • 將類型T的值轉(zhuǎn)換為類型T(允許但無(wú)用)
  • 使用類型斷言將接口值轉(zhuǎn)換為類型
  • 在類型switch中將類型用作case
  • 定義和使用這些類型的復(fù)合類型,例如該類型的切片
  • 將類型傳遞給一些內(nèi)置函數(shù)淮摔,例如 new

盡管目前預(yù)計(jì)不會(huì)進(jìn)行任何修改私沮,但將來(lái)的語(yǔ)言更改可能還會(huì)添加其他此類操作。

定義約束

Go已經(jīng)具有與我們需要的約束接近的構(gòu)造:接口類型(interface)和橙。接口類型是一組方法仔燕≡於猓可以分配給接口類型的變量的是那些實(shí)現(xiàn)相同方法的類型的值。只能使用接口類型執(zhí)行的操作是調(diào)用方法晰搀。

使用類型參數(shù)調(diào)用泛型函數(shù)類似于分配給接口類型的變量:類型參數(shù)必須實(shí)現(xiàn)類型參數(shù)的約束五辽。編寫泛型函數(shù)就像使用接口類型的值一樣:泛型代碼只能使用約束所允許的操作(或任何其他類型所允許的操作)。

在這種設(shè)計(jì)中外恕,約束只是接口類型杆逗。實(shí)現(xiàn)約束只是實(shí)現(xiàn)接口類型。(稍后鳞疲,我們將看到如何為方法定義約束)罪郊。

對(duì)于Stringify示例,我們需要一個(gè)接口類型建丧,該接口類型的String方法不帶任何參數(shù)并返回type的值string排龄。

// Stringer is a type constraint that requires the type argument to have
// a String method and permits the generic function to call String.
// The String method should return a string representation of the value.
type Stringer interface {
    String() string
}

(此討論無(wú)關(guān)緊要,但這定義了與標(biāo)準(zhǔn)庫(kù)的fmt.Stringer類型相同的接口翎朱,實(shí)際代碼可能只使用fmt.Stringer。)

使用約束

對(duì)于泛型函數(shù)尺铣,可以將約束視為類型實(shí)參的類型:元類型拴曲。因此,盡管不需要泛型函數(shù)使用約束凛忿,但泛型函數(shù)在使用時(shí)會(huì)在類型參數(shù)列表中作為類型參數(shù)的元類型列出澈灼。

// Stringify calls the String method on each element of s,
// and returns the results.
func Stringify(type T Stringer)(s []T) (ret []string) {
    for _, v := range s {
        ret = append(ret, v.String())
    }
    return ret
}

在這種情況下,單個(gè)類型參數(shù)T后跟適用的約束店溢。在這個(gè)例子里就是Stringer叁熔。

多種類型參數(shù)

盡管該Stringify示例僅使用單個(gè)類型參數(shù),但函數(shù)可能具有多個(gè)類型參數(shù)床牧。

// Print2 has two type parameters and two non-type parameters.
func Print2(type T1, T2)(s1 []T1, s2 []T2) { ... }

比較一下

// Print2Same has one type parameter and two non-type parameters.
func Print2Same(type T)(s1 []T, s2 []T) { ... }

Print2s1s2可以是不同類型的切片荣回。Print2Same中 s1和s2必須是相同元素類型的切片。

每個(gè)類型參數(shù)可能都有其自己的約束戈咳。

// Stringer is a type constraint that requires a String method.
// The String method should return a string representation of the value.
type Stringer interface {
    String() string
}

// Plusser is a type constraint that requires a Plus method.
// The Plus method is expected to add the argument to an internal
// string and return the result.
type Plusser interface {
    Plus(string) string
}

// ConcatTo takes a slice of elements with a String method and a slice
// of elements with a Plus method. The slices should have the same
// number of elements. This will convert each element of s to a string,
// pass it to the Plus method of the corresponding element of p,
// and return a slice of the resulting strings.
func ConcatTo(type S Stringer, P Plusser)(s []S, p []P) []string {
    r := make([]string, len(s))
    for i, v := range s {
        r[i] = p[i].Plus(v.String())
    }
    return r
}

如果為任何類型參數(shù)指定了約束心软,則每個(gè)類型參數(shù)都必須具有約束。如果某些類型參數(shù)需要約束而有些則不需要著蛙,則那些不需要的需要有一個(gè)interface{}約束删铃。

// StrAndPrint takes a slice of labels, which can be any type,
// and a slice of values, which must have a String method,
// converts the values to strings, and prints the labelled strings.
func StrAndPrint(type L interface{}, T Stringer)(labels []L, vals []T) {
    // Stringify was defined above. It returns a []string.
    for i, s := range Stringify(vals) {
        fmt.Println(labels[i], s)
    }
}

單個(gè)約束可用于多個(gè)類型參數(shù),就像單個(gè)類型可用于多個(gè)非類型函數(shù)參數(shù)一樣踏堡。約束分別應(yīng)用于每個(gè)類型參數(shù)猎唁。

// Stringify2 converts two slices of different types to strings,
// and returns the concatenation of all the strings.
func Stringify2(type T1, T2 Stringer)(s1 []T1, s2 []T2) string {
    r := ""
    for _, v1 := range s1 {
        r += v1.String()
    }
    for _, v2 := range s2 {
        r += v2.String()
    }
    return r
}

泛型類型

我們不僅需要泛型函數(shù):我們還需要泛型類型。我們建議將類型擴(kuò)展為可接受類型參數(shù)顷蟆。

// Vector is a name for a slice of any element type.
type Vector(type T) []T

類型的參數(shù)就像函數(shù)的類型參數(shù)一樣诫隅。

在類型定義內(nèi)缎患,可以像其他類型一樣使用。

要使用泛型類型阎肝,必須提供類型參數(shù)挤渔。這看起來(lái)像一個(gè)函數(shù)調(diào)用,只是在這種情況下該函數(shù)實(shí)際上是一個(gè)類型风题。這稱為實(shí)例化判导。當(dāng)通過(guò)為類型參數(shù)提供類型實(shí)參來(lái)實(shí)例化類型時(shí),我們會(huì)生成一個(gè)類型沛硅,其中類型定義中對(duì)類型參數(shù)的每次使用都將被相應(yīng)的類型實(shí)參替換眼刃。

// v is a Vector of int values.
//
// This is similar to pretending that "Vector(int)" is a valid identifier,
// and writing
//   type "Vector(int)" []int
//   var v "Vector(int)"
// All uses of Vector(int) will refer to the same "Vector(int)" type.
//
var v Vector(int)

泛型類型可以具有方法。方法的接收類型必須聲明與接收類型的定義中聲明的數(shù)量相同的類型參數(shù)摇肌。聲明它們時(shí)沒(méi)有type關(guān)鍵字或任何約束抛蚤。

// Push adds a value to the end of a vector.
func (v *Vector(T)) Push(x T) { *v = append(*v, x) }

方法聲明中列出的類型參數(shù)不必與類型聲明中的類型參數(shù)具有相同的名稱。特別是如果方法未使用它們悠瞬,則可以使用_酣衷。

如果一個(gè)類型可以引用自身,則泛型類型也可以引用自身肯适,但是當(dāng)這樣做時(shí)变秦,類型實(shí)參必須是按相同順序列出的類型形參。此限制可防止類型實(shí)例化的無(wú)限遞歸框舔。

// List is a linked list of values of type T.
type List(type T) struct {
    next *List(T) // this reference to List(T) is OK
    val  T
}

// This type is INVALID.
type P(type T1, T2) struct {
    F *P(T2, T1) // INVALID; must be (T1, T2)
}

此限制適用于直接和間接引用蹦玫。

// ListHead is the head of a linked list.
type ListHead(type T) struct {
    head *ListElement(T)
}

// ListElement is an element in a linked list with a head.
// Each element points back to the head.
type ListElement(type T) struct {
    next *ListElement(T)
    val  T
    // Using ListHead(T) here is OK.
    // ListHead(T) refers to ListElement(T) refers to ListHead(T).
    // Using ListHead(int) would not be OK, as ListHead(T)
    // would have an indirect reference to ListHead(int).
    head *ListHead(T)
}

(注意:在了解人們?nèi)绾尉帉懘a的情況下,可以放寬此規(guī)則刘绣,以允許某些情況下使用不同的類型參數(shù)樱溉。)

泛型類型的類型參數(shù)可能具有約束。

// StringableVector is a slice of some type, where the type
// must have a String method.
type StringableVector(type T Stringer) []T

func (s StringableVector(T)) String() string {
    var sb strings.Builder
    for i, v := range s {
        if i > 0 {
            sb.WriteString(", ")
        }
        // It's OK to call v.String here because v is of type T
        // and T's constraint is Stringer.
        sb.WriteString(v.String())
    }
    return sb.String()
}

不支持泛型方法

盡管泛型類型的方法可以使用該類型的參數(shù)纬凤,但是方法本身沒(méi)有附加的類型參數(shù)福贞。

在后面的[issues]章節(jié)對(duì)此進(jìn)行了更多討論。

操作符

如我們所見移斩,我們將接口類型用作約束肚医。接口類型提供了一組方法,僅此而已向瓷。這意味著到目前為止肠套,我們已經(jīng)看到,泛型函數(shù)可以對(duì)類型參數(shù)的值執(zhí)行的唯一操作(對(duì)任何類型都允許的操作除外)是調(diào)用方法猖任。

但是你稚,對(duì)于我們要表達(dá)的所有內(nèi)容,僅靠方法調(diào)用還不夠〉罄担考慮下面這個(gè)簡(jiǎn)單的函數(shù)搁痛,該函數(shù)返回值切片的最小元素,其中該切片被假定為非空宇弛。

// This function is INVALID.
func Smallest(type T)(s []T) T {
    r := s[0] // panic if slice is empty
    for _, v := range s[1:] {
        if v < r { // INVALID
            r = v
        }
    }
    return r
}

任何合理的泛型實(shí)現(xiàn)都會(huì)讓你編寫一個(gè)類似的函數(shù)鸡典。問(wèn)題是表達(dá)式v < r。這假定了T支持<運(yùn)算符枪芒,但T沒(méi)有約束彻况。在沒(méi)有約束的情況下,該函數(shù)Smallest只能使用可用于所有類型的操作舅踪,但是并不是所有的Go類型都支持操作<纽甘。不幸的是,由于<這不是一種方法抽碌,因此沒(méi)有明顯的方法來(lái)為<編寫允許的約束(接口類型)悍赢。

我們需要寫一種僅接受支持<類型的約束的方法。對(duì)此货徙,我們注意到左权,除了稍后將要討論的兩個(gè)例外,語(yǔ)言定義的所有算術(shù)破婆,比較和邏輯運(yùn)算符都只能與該語(yǔ)言預(yù)先聲明的類型或已定義的類型一起使用涮总。其基礎(chǔ)類型是那些預(yù)先聲明的類型之一。也就是說(shuō)祷舀,運(yùn)算符<只能與預(yù)先聲明的類型(例如intfloat64)一起使用,或者只能將其基礎(chǔ)類型為這些類型之一的已定義類型使用烹笔。Go不允許<與復(fù)合類型或任意定義的類型一起使用裳扯。

這意味著我們不必嘗試為<編寫約束,而是可以采用另一種方法:與其說(shuō)約束應(yīng)支持哪些運(yùn)算符谤职,不如說(shuō)約束應(yīng)接受哪些(底層)類型饰豺。

約束中的類型列表

用作約束的接口類型可以列出可以用作類型參數(shù)的顯式類型。這是通過(guò)使用type關(guān)鍵字后跟逗號(hào)分隔的類型列表來(lái)完成的允蜈。

例如:

// SignedInteger is a type constraint that permits any
// signed integer type.
type SignedInteger interface {
    type int, int8, int16, int32, int64
}

SignedInteger限制規(guī)定類型參數(shù)必須是所列出的類型之一冤吨。更準(zhǔn)確地說(shuō),類型實(shí)參的基礎(chǔ)類型必須與類型列表中類型之一的基礎(chǔ)類型相同饶套。這意味著SignedInteger將接受列出的整數(shù)類型漩蟆,并且還將接受定義為這些類型之一的任何類型。

當(dāng)泛型函數(shù)使用具有這些約束的類型參數(shù)時(shí)妓蛮,它可以使用所有列出的類型允許的任何操作怠李。這可以像操作<range<-捺癞,等等夷蚊。如果可以使用約束中列出的每種類型成功編譯函數(shù),則允許該操作髓介。

一個(gè)約束可能只有一個(gè)類型列表惕鼓。

對(duì)于前面顯示的Smallest示例,我們可以使用如下約束:

package constraints

// Ordered is a type constraint that matches any ordered type.
// An ordered type is one that supports the <, <=, >, and >= operators.
type Ordered interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

在實(shí)踐中唐础,很可能會(huì)定義此約束并將其導(dǎo)出到新的標(biāo)準(zhǔn)庫(kù)包constraints中箱歧,以便函數(shù)和類型定義可以使用它。

給定該約束彻犁,我們就可以編寫此函數(shù)叫胁,下面這個(gè)函數(shù)現(xiàn)在有效:

// Smallest returns the smallest element in a slice.
// It panics if the slice is empty.
func Smallest(type T constraints.Ordered)(s []T) T {
    r := s[0] // panics if slice is empty
    for _, v := range s[1:] {
        if v < r {
            r = v
        }
    }
    return r
}

約束中的可比較類型

前面我們提到,有兩個(gè)例外的規(guī)則汞幢,即運(yùn)算符只能與語(yǔ)言預(yù)先聲明的類型一起使用驼鹅。==!=是例外,結(jié)構(gòu)森篷,數(shù)組和接口類型均允許進(jìn)行比較操作输钩。如果我們希望能夠編寫一個(gè)接受任何可比較類型的約束,知道這一點(diǎn)就很有用了仲智。

為此买乃,我們引入了一個(gè)新的預(yù)先聲明的類型約束:comparable。具有comparable約束的類型參數(shù)接受任何可比較類型作為實(shí)參钓辆。它允許使用==剪验,!=與該類型參數(shù)的值進(jìn)行比較。

例如前联,可以使用任何可比較的類型實(shí)例化下面這個(gè)函數(shù):

// Index returns the index of x in s, or -1 if not found.
func Index(type T comparable)(s []T, x T) int {
    for i, v := range s {
        // v and x are type T, which has the comparable
        // constraint, so we can use == here.
        if v == x {
            return i
        }
    }
    return -1
}

與所有約束一樣功戚,由于comparable是接口類型,因此可以將其嵌入用作約束的另一種接口類型中:

// ComparableHasher is a type constraint that matches all
// comparable types with a Hash method.
type ComparableHasher interface {
    comparable
    Hash() uintptr
}

約束ComparableHasher可以通過(guò)任何可比較類型實(shí)現(xiàn)似嗤,也可以使用一種Hash() uintptr方法來(lái)實(shí)現(xiàn)啸臀。ComparableHasher用作約束的泛型函數(shù)可以比較該類型的值并可以調(diào)用該Hash方法。

接口類型中的類型列表

具有類型列表的接口類型只能用作對(duì)類型參數(shù)的約束烁落。它們可能不能用作普通接口類型乘粒。預(yù)先聲明的接口類型comparable也是如此。

在將來(lái)的語(yǔ)言版本中可能會(huì)取消此限制伤塌。具有類型列表的接口類型可用作求和類型的一種形式灯萍,盡管它可以具有value nil〈缑眨可能需要一些替代語(yǔ)法來(lái)匹配相同類型而不是基礎(chǔ)類型竟稳。也許會(huì)是type ==属桦。不過(guò)目前這是不允許的。

函數(shù)參數(shù)類型推斷

在許多情況下他爸,當(dāng)調(diào)用帶有類型參數(shù)的函數(shù)時(shí)聂宾,我們可以使用interface來(lái)避免顯式寫出類型參數(shù)。

回顧之前的Print方法:

    Print(int)([]int{1, 2, 3})

類型參數(shù)int中的函數(shù)調(diào)用可以從非類型參數(shù)的類型來(lái)推斷诊笤。

僅當(dāng)所有函數(shù)的類型參數(shù)都用于函數(shù)(非類型)輸入?yún)?shù)的類型時(shí)系谐,才可以這樣做。如果有一些類型參數(shù)僅用于函數(shù)的結(jié)果參數(shù)類型讨跟,或者僅在函數(shù)體中使用纪他,我們的算法無(wú)法推斷函數(shù)的類型參數(shù),因?yàn)闆](méi)有值可用來(lái)推斷類型晾匠。

當(dāng)可以推斷函數(shù)的類型參數(shù)時(shí)茶袒,語(yǔ)言便可以使用統(tǒng)一類型。在調(diào)用方凉馆,我們有實(shí)際(非類型)參數(shù)的類型列表薪寓,在Print例中為[]int。在函數(shù)這邊是類型函數(shù)的無(wú)類型參數(shù)澜共,這對(duì)于Print[]T向叉。在列表中,我們丟棄函數(shù)側(cè)不使用的類型參數(shù)的各個(gè)參數(shù)嗦董。然后母谎,我們必須統(tǒng)一其余的參數(shù)類型。

類型統(tǒng)一是一種two-pass算法京革。在第一遍中奇唤,我們忽略了調(diào)用方的無(wú)類型化常量及其在函數(shù)定義中的對(duì)應(yīng)類型。

我們?cè)诹斜碇斜容^相應(yīng)的類型匹摇。它們的結(jié)構(gòu)必須相同冻记,除非函數(shù)一側(cè)的類型參數(shù)與出現(xiàn)在調(diào)用者一側(cè)的類型參數(shù)相匹配。如果同一個(gè)類型參數(shù)在函數(shù)側(cè)出現(xiàn)多次来惧,它將與調(diào)用方側(cè)的多個(gè)參數(shù)類型匹配。這些調(diào)用者類型必須相同演顾,否則類型統(tǒng)一失敗我們將報(bào)告錯(cuò)誤供搀。

第一次通過(guò)后,我們?cè)谡{(diào)用方檢查所有未定義類型的常量钠至。如果沒(méi)有無(wú)類型的常量葛虐,或者相應(yīng)函數(shù)類型中的類型參數(shù)與其他輸入類型匹配,則類型統(tǒng)一完成棉钧。

否則屿脐,對(duì)于第二遍,對(duì)于尚未設(shè)置相應(yīng)函數(shù)類型的任何未類型化的常量,我們將以默認(rèn)的方式確定未類型常量的默認(rèn)類型的诵。然后我們?cè)俅芜\(yùn)行類型統(tǒng)一算法万栅,這次沒(méi)有任何未類型化的常量。

在這個(gè)例子中

    s1 := []int{1, 2, 3}
    Print(s1)

我們比較[]int[]T西疤,T匹配int烦粒。單一類型參數(shù)Tint,因此我們推斷對(duì)Print的調(diào)用實(shí)際上是對(duì)Print(int)的調(diào)用代赁。

對(duì)于更復(fù)雜的示例扰她,請(qǐng)考慮

// Map calls the function f on every element of the slice s,
// returning a new slice of the results.
func Map(type F, T)(s []F, f func(F) T) []T {
    r := make([]T, len(s))
    for i, v := range s {
        r[i] = f(v)
    }
    return r
}

這兩個(gè)類型參數(shù)FT都用于輸入?yún)?shù),因此可以進(jìn)行類型推斷芭碍。在調(diào)用中

strs := Map([]int{1, 2, 3}, strconv.Itoa)

我們將[]int[]F統(tǒng)一徒役,F匹配int。我們統(tǒng)一strconv.Itoa的類型窖壕,即func(int) stringfunc(F) T忧勿,Fint以及Tstring。類型參數(shù)F匹配兩次艇拍,兩次都匹配int狐蜕。匹配成功,因此Map的調(diào)用是調(diào)用Map(int, string)卸夕。

要查看有效的無(wú)類型常量規(guī)則层释,請(qǐng)考慮:

// NewPair returns a pair of values of the same type.
func NewPair(type F)(f1, f2 F) *Pair(F) { ... }

在上面的調(diào)用中,NewPair(1, 2)兩個(gè)參數(shù)都是未類型化的常量快集,因此在第一遍中都將忽略贡羔。第一次通過(guò)后就沒(méi)有什么可以統(tǒng)一的了,此時(shí)我們還有兩個(gè)未類型化的常量个初。兩者均設(shè)置為其默認(rèn)類型int乖寒。類型統(tǒng)一的第二次運(yùn)行用int匹配F,所以最終調(diào)用NewPair(int)(1, 2)院溺。

NewPair(1, int64(2))調(diào)用中楣嘁,第一個(gè)參數(shù)是無(wú)類型的常量,因此我們?cè)诘谝槐橹袑⑵浜雎哉湟荨H缓笪覀儗?code>int64與F統(tǒng)一逐虚。此時(shí)颈抚,未類型化常量相對(duì)應(yīng)的類型參數(shù)已完全確定钙蒙,因此最終調(diào)用為NewPair(int64)(1, int64(2))

NewPair(1, 2.5)調(diào)用中实辑,兩個(gè)參數(shù)都是未類型化的常量漱病,因此我們繼續(xù)進(jìn)行第二遍买雾。這次我們將第一個(gè)常數(shù)設(shè)置為int把曼,將第二個(gè)常數(shù)設(shè)置為float64。然后漓穿,我們嘗試Fintfloat64統(tǒng)一嗤军,統(tǒng)一失敗,因此我們報(bào)告編譯錯(cuò)誤器净。

注意型雳,類型推斷是在不考慮約束的情況下完成的。首先山害,我們使用類型推斷來(lái)確定要用于該函數(shù)的類型參數(shù)纠俭,然后,如果成功浪慌,則檢查這些類型參數(shù)是否實(shí)現(xiàn)了約束(如果有)冤荆。

請(qǐng)注意,在成功進(jìn)行類型推斷之后权纤,對(duì)于任何函數(shù)調(diào)用钓简,編譯器仍必須檢查是否參數(shù)分配是否合理。

(注意:類型推斷是一種便利功能汹想。盡管我們認(rèn)為這是一項(xiàng)重要功能外邓,但它并未為設(shè)計(jì)添加任何功能,只是為使用提供了便利古掏∷鸹埃可以從初始實(shí)現(xiàn)中將其省略,并查看是否被需要槽唾,也就是此功能不需要其他語(yǔ)法丧枪,并且可以生成更具可讀性的代碼。)

使用約束中引用自己的類型

對(duì)于泛型函數(shù)來(lái)說(shuō)庞萍,要求類型自變量及其方法本身就是類型的方法可能是有用的拧烦。例如這在比較方法中自然會(huì)出現(xiàn)。(請(qǐng)注意钝计,我們?cè)谶@里討論的是方法恋博,而不是運(yùn)算符。)假設(shè)我們要編寫一個(gè)Index方法私恬,該方法使用一種Equal方法來(lái)檢查它是否找到了所需的值交播。我們想這樣寫:

// Index returns the index of e in s, or -1 if not found.
func Index(type T Equaler)(s []T, e T) int {
    for i, v := range s {
        if e.Equal(v) {
            return i
        }
    }
    return -1
}

為了編寫Equaler約束,我們必須編寫一個(gè)可以引用傳入的類型實(shí)參的約束践付。我們無(wú)法直接實(shí)現(xiàn),但是我們可以做的是編寫一個(gè)使用類型形參的接口類型缺厉。

// Equaler is a type constraint for types with an Equal method.
type Equaler(type T) interface {
    Equal(T) bool
}

為此永高,Index將類型參數(shù)T傳遞給Equaler隧土。規(guī)則是如果類型約束具有單個(gè)類型參數(shù),并且在沒(méi)有顯式類型參數(shù)的情況下用于函數(shù)的類型參數(shù)列表命爬,則這個(gè)類型參數(shù)是要約束的類型參數(shù)曹傀。換句話說(shuō),在上面的Index定義中饲宛,約束Equaler被視為Equaler(T)皆愉。

此版本的Index可以與此處定義的equalInt類型一起使用:

// equalInt is a version of int that implements Equaler.
type equalInt int

// The Equal method lets equalInt implement the Equaler constraint.
func (a equalInt) Equal(b equalInt) bool { return a == b }

// indexEqualInts returns the index of e in s, or -1 if not found.
func indexEqualInt(s []equalInt, e equalInt) int {
    return Index(equalInt)(s, e)
}

在此示例中,當(dāng)傳遞equalIntIndex時(shí)艇抠,我們檢查是否equalInt實(shí)現(xiàn)了Equaler約束幕庐。由于Equaler有一個(gè)類型參數(shù),我們傳遞Index的類型參數(shù)到Equaler,equalInt作為類型實(shí)參家淤。這樣异剥,該約束Equaler(equalInt)可以由任何類型的Equal(equalInt) bool的方法滿足。equalInt類型具有一個(gè)Equal接受類型參數(shù)的方法equalInt絮重,因此一切都滿足要求冤寿,并且編譯成功。

類型參數(shù)的相互引用

在單個(gè)類型參數(shù)列表中青伤,約束可以引用任何其他的類型參數(shù)督怜,即使是稍后在同一列表中聲明的參數(shù)也是如此。(類型參數(shù)的范圍從參數(shù)列表的type關(guān)鍵字開始狠角,并擴(kuò)展到封閉函數(shù)或類型聲明的末尾号杠。)

例如,考慮一個(gè)關(guān)于圖的類包擎厢,其中包含可用于圖的通用算法究流。該算法使用兩種類型的,NodeEdge动遭。Node有一種方法Edges() []Edge芬探。Edge有一種方法Nodes() (Node, Node)。圖可以表示為[]Node厘惦。

這種簡(jiǎn)單的表示足以實(shí)現(xiàn)圖算法偷仿,例如找到最短路徑。

package graph

// NodeConstraint is the type constraint for graph nodes:
// they must have an Edges method that returns the Edge's
// that connect to this Node.
type NodeConstraint(type Edge) interface {
    Edges() []Edge
}

// EdgeConstraint is the type constraint for graph edges:
// they must have a Nodes method that returns the two Nodes
// that this edge connects.
type EdgeConstraint(type Node) interface {
    Nodes() (from, to Node)
}

// Graph is a graph composed of nodes and edges.
type Graph(type Node NodeConstraint(Edge), Edge EdgeConstraint(Node)) struct { ... }

// New returns a new graph given a list of nodes.
func New(
    type Node NodeConstraint(Edge), Edge EdgeConstraint(Node)) (
    nodes []Node) *Graph(Node, Edge) {
    ...
}

// ShortestPath returns the shortest path between two nodes,
// as a list of edges.
func (g *Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { ... }

這里有很多類型參數(shù)和實(shí)例化宵蕉。在GraphNode的約束酝静,被傳遞給類型約束NodeConstraintEdgeGraph的第二個(gè)類型參數(shù)。NodeConstraint用類型參數(shù)實(shí)例化Edge羡玛,因此我們看到Node必須有一個(gè)返回Edges的切片的方法Edge别智,這就是我們想要的。這個(gè)約束同樣適用Edge稼稿,對(duì)于New函數(shù)對(duì)是重復(fù)相同的類型參數(shù)和約束薄榛。我們并不是說(shuō)這很簡(jiǎn)單讳窟,而是我們認(rèn)為這是可能的。

值得注意的是敞恋,乍一看丽啡,這看起來(lái)像是接口類型的典型用法,NodeEdge是具有特定方法的非接口類型硬猫。為了使用graph.Graph补箍,用于NodeEdge的類型實(shí)參必須定義遵循確定模式的方法,但實(shí)際上不必使用接口類型啸蜜。特別是坑雅,這些方法不返回接口類型。

例如盔性,在其他軟件包中考慮以下類型定義:

// Vertex is a node in a graph.
type Vertex struct { ... }

// Edges returns the edges connected to v.
func (v *Vertex) Edges() []*FromTo { ... }

// FromTo is an edge in a graph.
type FromTo struct { ... }

// Nodes returns the nodes that ft connects.
func (ft *FromTo) Nodes() (*Vertex, *Vertex) { ... }

此處沒(méi)有接口類型霞丧,但是我們可以使用類型實(shí)參*Vertex*FromTo實(shí)例化graph.Graph

var g = graph.New(*Vertex, *FromTo)([]*Vertex{ ... })

*Vertex*FromTo不是接口類型冕香,但是當(dāng)一起使用時(shí)蛹尝,它們定義實(shí)現(xiàn)graph.Graph約束的方法。請(qǐng)注意悉尾,我們不能將VertexFromTo傳遞給graph.New突那,因?yàn)?code>Vertex和FromTo沒(méi)有實(shí)現(xiàn)約束。在指針類型*Vertex*FromTo定義了EdgesNodes的方法; 類型Vertex构眯,FromTo沒(méi)有任何方法愕难。

當(dāng)使用泛型接口類型作為約束時(shí),我們首先使用類型參數(shù)列表中提供的類型實(shí)參實(shí)例化該類型惫霸,然后將對(duì)應(yīng)的類型實(shí)參與實(shí)例化的約束進(jìn)行比較猫缭。在此示例中,to graph.New的類型實(shí)參Node具有一個(gè)約束NodeConstraint(Edge)壹店。當(dāng)我們使用Node類型實(shí)參*VertexEdge類型實(shí)參*FromTo進(jìn)行graph.New調(diào)用時(shí)猜丹,為了檢查對(duì)Node的約束,編譯器必須使用類型實(shí)參*FromTo實(shí)例化NodeConstraint硅卢。這就產(chǎn)生了一個(gè)實(shí)例化的約束射窒,在這種情況下,該約束是擁有Edges() []*FromToNode将塑,并且編譯器將驗(yàn)證*Vertex是否滿足約束脉顿。

雖然NodeEdge沒(méi)有使用接口類型進(jìn)行實(shí)例化,如果你喜歡shi使用接口類型也可以点寥。

type NodeInterface interface { Edges() []EdgeInterface }
type EdgeInterface interface { Nodes() (NodeInterface, NodeInterface) }

我們可以使用類型NodeInterfaceEdgeInterface實(shí)例化graph.Graph艾疟,因?yàn)樗鼈儗?shí)現(xiàn)了類型約束。沒(méi)有太多理由以這種方式實(shí)例化類型,但這是允許的汉柒。

類型參數(shù)引用其他類型參數(shù)的能力說(shuō)明了一個(gè)重要的觀點(diǎn):任何嘗試向Go添加泛型的要求都應(yīng)該是可以實(shí)例化具有多個(gè)類型參數(shù)的泛型代碼误褪,這些類型參數(shù)以相互引用,編譯器可以檢查碾褂。

指針?lè)椒?/h3>

在某些情況下,僅當(dāng)類型實(shí)參A具有在指針類型*A上定義的方法時(shí)历葛,泛型函數(shù)才能按預(yù)期工作正塌。在編寫期望修改調(diào)用值的方法的泛型函數(shù)時(shí)會(huì)遇到這種情況。

考慮下面函數(shù)的示例恤溶,該函數(shù)期望一種類型T乓诽,該類型具有一種基于字符串初始化值的Set(string)方法。

// Setter is a type constraint that requires that the type
// implement a Set method that sets the value from a string.
type Setter interface {
    Set(string)
}

// FromStrings takes a slice of strings and returns a slice of T,
// calling the Set method to set each returned value.
//
// Note that because T is only used for a result parameter,
// type inference does not work when calling this function.
// The type argument must be passed explicitly at the call site.
//
// This example compiles but is unlikely to work as desired.
func FromStrings(type T Setter)(s []string) []T {
    result := make([]T, len(s))
    for i, v := range s {
        result[i].Set(v)
    }
    return result
}

現(xiàn)在咒程,讓我們看看其他程序包中的一些代碼(此示例無(wú)效)鸠天。

// Settable is a integer type that can be set from a string.
type Settable int

// Set sets the value of *p from a string.
func (p *Settable) Set(s string) {
    i, _ := strconv.Atoi(s) // real code should not ignore the error
    *p = Settable(i)
}

func F() {
    // INVALID
    nums := FromStrings(Settable)([]string{"1", "2"})
    // Here we want nums to be []Settable{1, 2}.
    ...
}

我們希望是用FromStrings來(lái)獲取[]Settable的切片。不幸的是帐姻,該示例無(wú)效稠集,無(wú)法編譯。

問(wèn)題是FromStrings需要具有Set(string)方法的類型饥瓷。函數(shù)F是試圖通過(guò)Settable實(shí)例化FromStrings剥纷,但Settable沒(méi)有Set方法。有Set方法的類型是*Settable呢铆。

因此晦鞋,讓我們使用*Settable改寫F

func F() {
    // Compiles but does not work as desired.
    // This will panic at run time when calling the Set method.
    nums := FromStrings(*Settable)([]string{"1", "2"})
    ...
}

這可以成功編譯棺克,但不幸的是它將在運(yùn)行時(shí)拋出異常悠垛。問(wèn)題是FromStrings創(chuàng)建了[]T切片。當(dāng)使用*Settable實(shí)例化時(shí)娜谊,表示類型為 []*Settable确买。當(dāng)FromStrings調(diào)用result[i].Set(v)時(shí),會(huì)將存儲(chǔ)在result[i]中的指針傳遞給Set方法因俐。那個(gè)指針是nil拇惋。Settable.Set方法將由nil接收方調(diào)用,并導(dǎo)致空指針異常抹剩。

我們需要的是寫一種FromStrings方法撑帖,使得它可以將類型Settable作為參數(shù)但可以調(diào)用指針?lè)椒ā?qiáng)調(diào)一遍澳眷,我們不能使用Settable因?yàn)樗鼪](méi)有Set方法胡嘿,我們也不能使用*Settable因?yàn)槟菢游覀兙筒荒軇?chuàng)建Settable切片。

一種可行的方法是使用兩個(gè)不同的類型參數(shù):Settable*Settable钳踊。

package from

// Setter2 is a type constraint that requires that the type
// implement a Set method that sets the value from a string,
// and also requires that the type be a pointer to its type parameter.
type Setter2(type B) interface {
    Set(string)
    type *B
}

// FromStrings2 takes a slice of strings and returns a slice of T,
// calling the Set method to set each returned value.
//
// We use two different type parameters so that we can return
// a slice of type T but call methods on *T.
// The Setter2 constraint ensures that PT is a pointer to T.
func FromStrings2(type T interface{}, PT Setter2(T))(s []string) []T {
    result := make([]T, len(s))
    for i, v := range s {
        // The type of &result[i] is *T which is in the type list
        // of Setter2, so we can convert it to PT.
        p := PT(&result[i])
        // PT has a Set method.
        p.Set(v)
    }
    return result
}

我們可以這樣調(diào)用FromStrings2

func F2() {
    // FromStrings2 takes two type parameters.
    // The second parameter must be a pointer to the first.
    // Settable is as above.
    nums := FromStrings2(Settable, *Settable)([]string{"1", "2"})
    // Now nums is []Settable{1, 2}.
    ...
}

這種方法可以正常工作衷敌,但是很尷尬勿侯。它通過(guò)強(qiáng)制傳遞兩個(gè)類型參數(shù)解決FromStrings2。第二個(gè)類型參數(shù)必須是第一個(gè)類型參數(shù)的指針缴罗。這是一個(gè)復(fù)雜的要求助琐,因?yàn)榭雌饋?lái)應(yīng)該是一個(gè)相當(dāng)簡(jiǎn)單的情況。

另一種方法是傳遞函數(shù)而不是調(diào)用方法面氓。

// FromStrings3 takes a slice of strings and returns a slice of T,
// calling the set function to set each returned value.
func FromStrings3(type T)(s []string, set func(*T, string)) []T {
    results := make([]T, len(s))
    for i, v := range s {
        set(&results[i], v)
    }
    return results
}

我們可以像下面這樣調(diào)用Strings3:

func F3() {
    // FromStrings3 takes a function to set the value.
    // Settable is as above.
    nums := FromStrings3(Settable)([]string{"1", "2"},
        func(p *Settable, s string) { p.Set(s) })
    // Now nums is []Settable{1, 2}.
}

這種方法也可以按預(yù)期工作兵钮,但也很尷尬。調(diào)用者必須傳遞一個(gè)函數(shù)才能調(diào)用Set方法舌界。這是我們?cè)谑褂梅盒蜁r(shí)希望避免的樣板代碼掘譬。

盡管這些方法很尷尬,但它們確實(shí)有效呻拌。就是說(shuō)葱轩,我們建議另一個(gè)解決此類問(wèn)題的功能:一種表示對(duì)類型參數(shù)的指針(而不是對(duì)類型參數(shù)本身)的約束的方法。完成此操作的方法是將type參數(shù)寫為指針類型:(type *T Constraint)藐握。

在類型參數(shù)列表中寫*T而不是T會(huì)改變兩件事靴拱。假設(shè)調(diào)用的類型參數(shù)為A,并且約束為Constraint(可以使用此語(yǔ)法而沒(méi)有約束趾娃,但是沒(méi)有理由這樣做)缭嫡。

更改的第一件事Constraint是應(yīng)用于*A而不是A曹仗。即*A必須實(shí)現(xiàn)Constraint慨菱。A可以實(shí)現(xiàn)Constraint垃它,但是要求是*A實(shí)現(xiàn)它瓤狐。請(qǐng)注意梆造,如果Constraint有任何方法待德,則意味著A一定不能是指針類型:如果A是指針類型丁眼,則*A是指向指針的指針锨亏,并且此類永遠(yuǎn)不會(huì)有任何方法炕泳。

更改的第二件事是纵诞,在函數(shù)體內(nèi)的任何方法Constraint都被視為指針?lè)椒āV荒茉趖ype的*T值或type的可尋址值上調(diào)用它們T培遵。

// FromStrings takes a slice of strings and returns a slice of T,
// calling the Set method to set each returned value.
//
// We write *T, meaning that given a type argument A,
// the pointer type *A must implement Setter.
//
// Note that because T is only used for a result parameter,
// type inference does not work when calling this function.
// The type argument must be passed explicitly at the call site.
func FromStrings(type *T Setter)(s []string) []T {
    result := make([]T, len(s))
    for i, v := range s {
        // result[i] is an addressable value of type T,
        // so it's OK to call Set.
        result[i].Set(v)
    }
    return result
}

同樣浙芙,使用*T意味著給定類型參數(shù)A,該類型*A必須實(shí)現(xiàn)約束Setter籽腕。在這種情況下嗡呼,Set必須位于*A中。在內(nèi)部FromStrings皇耗,使用 *T表示Set只能在T的可尋址值上調(diào)用南窗。

我們現(xiàn)在可以這樣使用

func F() {
    // With the rewritten FromStrings, this is now OK.
    // *Settable implements Setter.
    nums := from.Strings(Settable)([]string{"1", "2"})
    // Here nums is []Settable{1, 2}.
    ...
}

要明確的是,使用type *T Setter并不意味著Set方法只能是指針?lè)椒ā?code>Set仍然可能是一種普通方法。這樣就可以了万伤,因?yàn)樗兄祩鬟f方法也都在指針類型的方法集中窒悔。在此示例中,只有Set可以將其編寫為值方法才有意義敌买,在包含指針字段的結(jié)構(gòu)上定義方法時(shí)可能就是這種情況简珠。

使用泛型類型作為匿名函數(shù)參數(shù)類型

當(dāng)將實(shí)例化類型解析為匿名函數(shù)參數(shù)類型時(shí),存在解析歧義虹钮。

var f func(x(T))

在此示例中北救,我們不知道該函數(shù)是否具有實(shí)例化類型的單個(gè)匿名參數(shù)x(T),或者這是否是(T)類型的命名參數(shù)x芜抒。

我們希望這表示前者:實(shí)例化類型的匿名參數(shù)x(T)。實(shí)際上托启,這當(dāng)前語(yǔ)言并不向后兼容宅倒,這意味著后者。但是屯耸,當(dāng)前gofmt程序會(huì)重寫func(x(T))func(x T)拐迁,因此func(x(T))在普通的Go代碼中非常不尋常。

因此疗绣,我們建議更改語(yǔ)言线召。這可能會(huì)破壞一些現(xiàn)有程序,但解決方法是簡(jiǎn)單地運(yùn)行g(shù)ofmt多矮。這可能會(huì)改變編寫沒(méi)有使用gofmt的func(x(T))的程序的含義缓淹,而是選擇引入x與帶有括號(hào)類型的函數(shù)參數(shù)同名的泛型。我們認(rèn)為塔逃,此類程序?qū)O為罕見讯壶。

盡管如此,這仍然是一種風(fēng)險(xiǎn)湾盗,如果風(fēng)險(xiǎn)似乎太大伏蚊,我們可以避免進(jìn)行此更改。

類型參數(shù)的值不被“裝箱”(boxed)

在Go的當(dāng)前實(shí)現(xiàn)中格粪,接口值始終包含指針躏吊。將非指針值放在接口變量中會(huì)導(dǎo)致該值被裝箱。這意味著實(shí)際值存儲(chǔ)在堆或堆棧上的其他位置帐萎,接口值保存指向該位置的指針比伏。

在這種設(shè)計(jì)中,泛型的值不會(huì)裝箱吓肋。例如凳怨,讓我們回顧一下前面的示例from.Strings。當(dāng)用Settable實(shí)例化時(shí),它返回[]Settable肤舞。例如紫新,我們可以寫

// Settable is an integer type that can be set from a string.
type Settable int

// Set sets the value of *p from a string.
func (p *Settable) Set(s string) (err error) {
    // same as above
}

func F() {
    // The type of nums is []Settable.
    nums, err := from.Strings(Settable)([]string{"1", "2"})
    if err != nil { ... }
    // Settable can be converted directly to int.
    // This will set first to 1.
    first := int(nums[0])
    ...
}

當(dāng)我們用Settable類型調(diào)用from.Strings時(shí),我們得到一個(gè)[]Settable(和一個(gè)錯(cuò)誤)李剖。該切片的元素將是Settable值芒率,也就是說(shuō),它們將是整數(shù)篙顺。即使它們是由泛型函數(shù)創(chuàng)建和設(shè)置的偶芍,也不會(huì)被裝箱。

同樣德玫,當(dāng)實(shí)例化泛型類型時(shí)匪蟀,它將具有預(yù)期的類型作為組件。

type Pair(type F1, F2) struct {
    first  F1
    second F2
}

實(shí)例化該字段時(shí)宰僧,這些字段將不會(huì)被裝箱材彪,并且不會(huì)發(fā)生額外的內(nèi)存分配。類型Pair(int, string)可以轉(zhuǎn)換為struct { first int; second string }琴儿。

有關(guān)類型列表的更多信息

現(xiàn)在讓我們回顧類型列表段化,看一些相對(duì)的次要細(xì)節(jié)。這些不是額外規(guī)則或概念造成,而是類型列表工作方式的結(jié)果显熏。

約束中的類型列表和方法

約束可以同時(shí)使用類型列表和方法。

// StringableSignedInteger is a type constraint that matches any
// type that is both 1) defined as a signed integer type;
// 2) has a String method.
type StringableSignedInteger interface {
    type int, int8, int16, int32, int64
    String() string
}

此約束允許其基礎(chǔ)類型是列出的類型之一的任何類型晒屎,只要它也具有String() string方法喘蟆。值得注意的是,雖然StringableSignedInteger約束明確列出int夷磕,類型int不會(huì)本身允許作為類型參數(shù)履肃,因?yàn)?code>int沒(méi)有一個(gè)String方法。允許的類型參數(shù)的示例是MyInt坐桩,定義如下:

// MyInt is a stringable int.
type MyInt int

// The String method returns a string representation of mi.
func (mi MyInt) String() string {
    return fmt.Sprintf("MyInt(%d)", mi)
}

約束中的復(fù)合類型

約束中的類型可以是字面上的類型尺棋。

type byteseq interface {
    type string, []byte
}

遵循通常的規(guī)則:此約束的type參數(shù)可以是string[]byte或定義為這些類型之一的類型;具有此約束的泛型函數(shù)可以使用string和允許的任何操作[]byte绵跷。

byteseq約束允許編寫可用于string[]byte類型的通用函數(shù)膘螟。

// Join concatenates the elements of its first argument to create a
// single value. sep is placed between elements in the result.
// Join works for string and []byte types.
func Join(type T byteseq)(a []T, sep T) (ret T) {
    if len(a) == 0 {
        // Use the result parameter as a zero value;
        // see discussion of zero value in the Issues section.
        return ret
    }
    if len(a) == 1 {
        // We know that a[0] is either a string or a []byte.
        // We can append either a string or a []byte to a []byte,
        // producing a []byte. We can convert that []byte to
        // either a []byte (a no-op conversion) or a string.
        return T(append([]byte(nil), a[0]...))
    }
    // We can call len on sep because we can call len
    // on both string and []byte.
    n := len(sep) * (len(a) - 1)
    for _, v := range a {
        // Another case where we call len on string or []byte.
        n += len(v)
    }

    b := make([]byte, n)
    // We can call copy to a []byte with an argument of
    // either string or []byte.
    bp := copy(b, a[0])
    for _, s := range a[1:] {
        bp += copy(b[bp:], sep)
        bp += copy(b[bp:], s)
    }
    // As above, we can convert b to either []byte or string.
    return T(b)
}

類型列表中的類型參數(shù)

約束中的類型可以引用約束的類型參數(shù)。在此示例中碾局,泛型函數(shù)Map采用兩個(gè)類型參數(shù)荆残。要求第一類型參數(shù)具有作為第二類型參數(shù)的切片的基礎(chǔ)類型。第二個(gè)slice參數(shù)沒(méi)有限制净当。

// SliceConstraint is a type constraint that matches a slice of
// the type parameter.
type SliceConstraint(type T) interface {
    type []T
}

// Map takes a slice of some element type and a transformation function,
// and returns a slice of the function applied to each element.
// Map returns a slice that is the same type as its slice argument,
// even if that is a defined type.
func Map(type S SliceConstraint(E), E interface{})(s S, f func(E) E) S {
    r := make(S, len(s))
    for i, v := range s {
        r[i] = f(v)
    }
    return r
}

// MySlice is a simple defined type.
type MySlice []int

// DoubleMySlice takes a value of type MySlice and returns a new
// MySlice value with each element doubled in value.
func DoubleMySlice(s MySlice) MySlice {
    v := Map(MySlice, int)(s, func(e int) int { return 2 * e })
    // Here v has type MySlice, not type []int.
    return v
}

類型轉(zhuǎn)換

在具有兩個(gè)類型參數(shù)的函數(shù)FromTo内斯,如果接受的From約束可以被轉(zhuǎn)換類型To的約束蕴潦,類型的值From可以被轉(zhuǎn)換成類型的值To。如果任何一個(gè)類型參數(shù)不接受類型俘闯,則不允許類型轉(zhuǎn)換潭苞。

通用規(guī)則的結(jié)果,通用規(guī)則可以使用類型列表中列出的所有類型允許的任何操作真朗。

例如:

type integer interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr
}

func Convert(type To, From integer)(from From) To {
    to := To(from)
    if From(to) != from {
        panic("conversion out of range")
    }
    return to
}

Convert允許進(jìn)行類型轉(zhuǎn)換此疹,因?yàn)镚o允許將每個(gè)整數(shù)類型都轉(zhuǎn)換為其他整數(shù)類型。

無(wú)類型常量

某些函數(shù)可以使用無(wú)類型的常量遮婶。如果類型參數(shù)約束所接受的每種類型都允許使用無(wú)類型常量蝗碎。

與類型轉(zhuǎn)換一樣,可以使用類型列表中列出的所有類型所允許的任何操作旗扑。

type integer interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr
}

func Add10(type T integer)(s []T) {
    for i, v := range s {
        s[i] = v + 10 // OK: 10 can convert to any integer type
    }
}

// This function is INVALID.
func Add1024(type T integer)(s []T) {
    for i, v := range s {
        s[i] = v + 1024 // INVALID: 1024 not permitted by int8/uint8
    }
}

類型列表中復(fù)合類型的注釋

尚不清楚我們是否完全理解類型列表中復(fù)合類型的使用蹦骑。例如

type structField interface {
    type struct { a int; x int },
        struct { b int; x float64 },
        struct { c int; x uint64 }
}

func IncrementX(type T structField)(p *T) {
    v := p.x
    v++
    p.x = v
}

這種對(duì)IncrementX的類型參數(shù)的約束使得每個(gè)有效的類型參數(shù)都是具有某些數(shù)字類型字段x的結(jié)構(gòu)。因此臀防,很容易說(shuō)這IncrementX是有效的功能脊串。這意味著的類型v是基于類型參數(shù)的類型,隱式約束為interface { type int, float64, uint64 }清钥。這可能會(huì)變得相當(dāng)復(fù)雜,并且這里可能有一些我們不了解的細(xì)節(jié)放闺。

初始實(shí)現(xiàn)可能根本不支持類型列表中的復(fù)合類型祟昭,盡管這會(huì)使前面顯示的Join示例無(wú)效。

嵌入約束中的類型列表

當(dāng)一個(gè)約束嵌入另一個(gè)約束時(shí)怖侦,最終約束的類型列表就是所涉及的所有類型列表的交集。如果存在多個(gè)嵌入式類型,則交集保留的類型參數(shù)必須滿足所有嵌入式類型要求的屬性坊夫。

// Addable is types that support the + operator.
type Addable interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64, complex64, complex128,
        string
}

// Byteseq is a byte sequence: either string or []byte.
type Byteseq interface {
    type string, []byte
}

// AddableByteseq is a byte sequence that supports +.
// This is every type is that is both Addable and Byteseq.
// In other words, just the type string.
type AddableByteseq interface {
    Addable
    Byteseq
}

類型列表的一般說(shuō)明

在約束中顯式列出類型似乎很尷尬碌秸,但是這樣很清楚,在調(diào)用上允許使用哪些類型參數(shù)艳悔,以及泛型函數(shù)允許使用哪些操作急凰。

如果語(yǔ)言后來(lái)更改為支持運(yùn)算符方法(目前尚無(wú)此類計(jì)劃),則約束將像處理其他方法一樣處理它們猜年。

預(yù)聲明類型總是會(huì)有數(shù)量限制抡锈,以及這些類型支持的數(shù)量有限的運(yùn)算符。將來(lái)的語(yǔ)言更改不會(huì)從根本上改變這些事實(shí)乔外,因此此方法將繼續(xù)有用床三。

這種方法不會(huì)嘗試處理所有可能的運(yùn)算符。尚不清楚它是否適用于復(fù)合類型杨幼。期望這些將在泛型函數(shù)和類型聲明中使用復(fù)合類型來(lái)處理聂渊,而不是要求復(fù)合類型作為類型參數(shù)。例如四瘫,我們希望要索引到切片T的函數(shù)在slice元素類型上進(jìn)行參數(shù)化汉嗽,并使用type的參數(shù)或變量[]T

上面的示例DoubleMySlice所示莲组,這種方法使聲明接受并返回復(fù)合類型并想要返回與其參數(shù)類型相同的結(jié)果類型的泛型函數(shù)變得笨拙诊胞。定義的復(fù)合類型并不常見,但確實(shí)會(huì)出現(xiàn)锹杈。這種尷尬是這種方法的缺點(diǎn)撵孤。

反射

我們不建議以任何方式更改反射包。當(dāng)實(shí)例化類型或函數(shù)時(shí)竭望,所有類型參數(shù)將變?yōu)槠胀ǖ姆欠盒皖愋托奥搿?shí)例化類型Stringreflect.Type值的方法將返回在括號(hào)中帶有類型參數(shù)的名稱。例如咬清,List(int)闭专。

非泛型代碼不可能在不實(shí)例化的情況下引用泛型代碼,因此旧烧,沒(méi)有實(shí)例化的泛型類型或函數(shù)沒(méi)有反射信息影钉。

實(shí)踐

Russ Cox 的著作指出,泛型需要在慢速的程序員掘剪,慢速的編譯器或慢速的執(zhí)行時(shí)間之間進(jìn)行選擇平委。

我們認(rèn)為該設(shè)計(jì)允許不同的實(shí)現(xiàn)選擇《崴可以為每組類型實(shí)參分別編譯代碼廉赔,或者可以像對(duì)每個(gè)類型實(shí)參以類似于方法調(diào)用的接口類型進(jìn)行處理一樣進(jìn)行編譯,或者可以將兩者組合在一起匾鸥。

換句話說(shuō)蜡塌,這種設(shè)計(jì)放棄了讓程序員編程更慢,并絕對(duì)在慢速的編譯器(分別編譯每個(gè)類型的參數(shù)集)或慢速的執(zhí)行時(shí)間(對(duì)類型參數(shù)的值的每個(gè)操作使用方法調(diào)用)之間進(jìn)行決定勿负。 )馏艾。

總結(jié)

盡管本文檔冗長(zhǎng)且過(guò)于細(xì)節(jié),但實(shí)際設(shè)計(jì)僅涉及幾個(gè)要點(diǎn)奴愉。

  • 函數(shù)和類型可以具有類型參數(shù)攒至,該類型參數(shù)是使用可選約束(接口類型)定義的。
  • 約束描述了類型參數(shù)所需的方法和允許的類型躁劣。
  • 約束描述了類型參數(shù)允許的方法和操作迫吐。
  • 當(dāng)使用類型參數(shù)調(diào)用函數(shù)時(shí),類型推斷通常會(huì)允許省略類型參數(shù)账忘。

此設(shè)計(jì)完全向后兼容志膀,除了一個(gè)關(guān)于func F(x(T))的修改建議熙宇。

我們相信,這種設(shè)計(jì)可以滿足人們對(duì)Go中泛型編程的需求溉浙,而不會(huì)使該語(yǔ)言變得不必要的復(fù)雜烫止。

在沒(méi)有多年實(shí)踐經(jīng)驗(yàn)的基礎(chǔ)上,我們無(wú)法真正了解這些對(duì)語(yǔ)言的影響戳稽。這里有一些猜測(cè)馆蠕。

復(fù)雜

Go的優(yōu)點(diǎn)之一就是它的簡(jiǎn)單性。顯然惊奇,這種設(shè)計(jì)使語(yǔ)言更加復(fù)雜互躬。

我們認(rèn)為,對(duì)于閱讀良好書面通用代碼而不是編寫書面代碼的人們來(lái)說(shuō)颂郎,增加的復(fù)雜性很小吼渡。人們自然必須學(xué)習(xí)用于聲明類型參數(shù)的新語(yǔ)法。這種新的語(yǔ)法以及對(duì)接口中類型列表的新支持是該設(shè)計(jì)中唯一的新語(yǔ)法構(gòu)造乓序。如下面的示例所示寺酪,泛型函數(shù)中的代碼讀取方式與普通的Go代碼類似。從[]int[]T是一個(gè)簡(jiǎn)單的轉(zhuǎn)變替劈。類型參數(shù)約束可以有效地用作描述類型的文檔寄雀。

我們期望大多數(shù)軟件包不會(huì)定義泛型類型或函數(shù),但是許多軟件包可能會(huì)使用在其他地方定義的泛型類型或函數(shù)陨献。在通常情況下咙俩,泛型函數(shù)的工作原理與非泛型函數(shù)完全相同:你只需調(diào)用它們即可。類型推斷意味著你不必顯式寫出類型參數(shù)湿故。類型推斷規(guī)則的設(shè)計(jì)是毫不奇怪的:正確推斷出類型實(shí)參,或者調(diào)用失敗并需要顯式類型變量膜蛔。類型推斷使用類型標(biāo)識(shí)坛猪,沒(méi)有嘗試解析兩個(gè)相似但不相同的類型,這消除了相當(dāng)大的復(fù)雜性皂股。

使用泛型類型的程序包將必須傳遞顯式類型參數(shù)墅茉。語(yǔ)法很熟悉。唯一的變化是將參數(shù)傳遞給類型呜呐,而不是僅傳遞給函數(shù)就斤。

總的來(lái)說(shuō),我們?cè)噲D避免設(shè)計(jì)上的一些大動(dòng)作蘑辑。只有時(shí)間才能證明我們的設(shè)計(jì)是否成功洋机。

普及推廣

我們希望將一些新軟件包添加到標(biāo)準(zhǔn)庫(kù)中。一個(gè)新的slices包將類似于現(xiàn)有的byte和string包洋魂,可在任何元素類型的切片上運(yùn)行绷旗。新 mapschans包將提供簡(jiǎn)單的算法喜鼓,這些算法當(dāng)前已針對(duì)每種元素類型進(jìn)行了重復(fù)實(shí)現(xiàn)。set可以添加一個(gè)程序包衔肢。

constraints程序包將提供標(biāo)準(zhǔn)約束庄岖,例如允許所有整數(shù)類型或所有數(shù)字類型的約束。

像包container/listcontainer/ring角骤,和類型隅忿,如sync.Mapsync/atomic.Value,將被更新為編譯時(shí)類型安全的邦尊,或者使用新的名稱或軟件包的更新版本背桐。

math軟件包將得到擴(kuò)展,為所有數(shù)值類型(例如流行MinMax函數(shù))提供一組簡(jiǎn)單的標(biāo)準(zhǔn)算法胳赌。

我們可能會(huì)在sort包裝中添加泛型變量牢撼。

可能會(huì)開發(fā)新的特殊用途的編譯時(shí)類型安全的容器類型。

我們不希望像C ++ STL迭代器類型這樣的方法被廣泛使用疑苫。在Go中熏版,這種想法更自然地使用接口類型表達(dá)。用C ++術(shù)語(yǔ)來(lái)說(shuō)捍掺,將接口類型用于迭代器可能帶有抽象損失撼短,因?yàn)檫\(yùn)行時(shí)效率將低于實(shí)際上內(nèi)聯(lián)所有代碼的C ++方法。

隨著我們獲得更多的容器類型挺勿,我們可能會(huì)開發(fā)一個(gè)標(biāo)準(zhǔn)Iterator接口曲横。這可能反過(guò)來(lái)導(dǎo)致施加壓力,要求修改語(yǔ)言以添加一些Iterator與with range子句一起使用的機(jī)制不瓶。不過(guò)禾嫉,這是非常投機(jī)的。

效率

人們希望通過(guò)泛型代碼提高哪種效率尚不清楚蚊丐。

可以使用基于接口的方法來(lái)編譯泛型函數(shù)而不是泛型類型熙参。這樣可以優(yōu)化編譯時(shí)間,因?yàn)樵摵瘮?shù)僅編譯一次,但是會(huì)花費(fèi)一些運(yùn)行時(shí)間。

對(duì)于每個(gè)類型參數(shù)集洞辣,泛型類型可能會(huì)多次編譯押逼。顯然,這將帶來(lái)編譯時(shí)間成本,但不會(huì)帶來(lái)任何運(yùn)行時(shí)間成本。編譯器還可以選擇使用類似于接口類型的方法來(lái)實(shí)現(xiàn)泛型類型,使用專用方法訪問(wèn)依賴于類型參數(shù)的每個(gè)元素锐涯。

只有總結(jié)經(jīng)驗(yàn)才能知道人們對(duì)該領(lǐng)域的期望。

遺漏

我們認(rèn)為該設(shè)計(jì)涵蓋了泛型編程的基本要求填物。但是秀仲,還有許多不支持的構(gòu)造。

  • 無(wú)法編寫與特定類型參數(shù)一起使用的泛型函數(shù)的多個(gè)版本壶笼。
  • 沒(méi)有辦法編寫在編譯時(shí)執(zhí)行代碼以生成要在運(yùn)行時(shí)執(zhí)行的代碼神僵。
  • 沒(méi)有更高級(jí)別的抽象。除了調(diào)用或?qū)嵗瘮?shù)外覆劈,沒(méi)有其他方法可以討論帶有類型參數(shù)的函數(shù)保礼。除了實(shí)例化泛型之外,沒(méi)有其他方法可以討論责语。
  • 沒(méi)有泛型類型的描述炮障。為了在泛型函數(shù)中使用運(yùn)算符,約束列出了特定的類型坤候,而不是描述類型必須具有的特征胁赢。這很容易理解白筹,但有時(shí)可能會(huì)受到限制。
  • 參數(shù)不支持協(xié)變或逆變(Contravariance or Covariance)。
  • 你可以編寫一個(gè)編譯時(shí)類型安全的通用容器,但只能使用普通方法而不是使用c[k]語(yǔ)法來(lái)訪問(wèn)它。
  • 除了使用輔助函數(shù)或包裝器類型外土辩,沒(méi)有其他方法指定一些類型參數(shù)启涯。
  • 不支持可變參數(shù)類型參數(shù),只能通過(guò)函數(shù)接受不同數(shù)量的類型參數(shù)和常規(guī)參數(shù)。
  • 沒(méi)有適配器。約束無(wú)法定義可用于支持尚未實(shí)現(xiàn)約束的類型參數(shù)的適配器,例如,根據(jù)==方法定義Equal運(yùn)算符,反之亦然雅任。
  • 沒(méi)有對(duì)非類型值(例如常量)進(jìn)行參數(shù)化禽车。這對(duì)于數(shù)組來(lái)說(shuō)最明顯逸月,有時(shí)可能很方便編寫type Matrix(type n int) [n][n]float64。為容器類型指定有效值有時(shí)也很有用,例如元素的默認(rèn)值。

Issues

此設(shè)計(jì)存在一些問(wèn)題,值得更詳細(xì)的討論窒朋。與整體設(shè)計(jì)相比欺劳,我們認(rèn)為這些問(wèn)題相對(duì)較小,但是仍然值得我們進(jìn)行完整的思考和討論。

零值

該設(shè)計(jì)沒(méi)有簡(jiǎn)單的表達(dá)式來(lái)表示類型參數(shù)的零值。例如递惋,考慮使用指針的可選值的以下實(shí)現(xiàn):

type Optional(type T) struct {
    p *T
}

func (o Optional(T)) Val() T {
    if o.p != nil {
        return *o.p
    }
    var zero T
    return zero
}

o.p == nil的情況下,我們想要返回T的零值萍虽,但是我們無(wú)法編寫該值睛廊。可能寫為return nil會(huì)很好,但是如果Tint财异,那是行不通的。在那種情況下一疯,我們將不得不寫return 0十偶。而且狱窘,當(dāng)然躲胳,沒(méi)有辦法編寫約束來(lái)支持return nilreturn 0

一些解決方法是:

  • var zero T如上使用俊扭,它可以與現(xiàn)有設(shè)計(jì)一起使用溺蕉,但需要另外聲明伶丐。
  • 使用*new(T),這很神秘疯特,但可以與現(xiàn)有設(shè)計(jì)一起使用哗魂。
  • 僅針對(duì)結(jié)果,將結(jié)果參數(shù)命名為_漓雅,并使用裸return語(yǔ)句返回零值录别。
  • 擴(kuò)展設(shè)計(jì)以允許使用nil任何匹配通用類型的零值(但請(qǐng)參見問(wèn)題22729)。
  • 擴(kuò)展設(shè)計(jì)以允許使用T{}邻吞,其中T是類型參數(shù)组题,以指示類型的零值。
  • 問(wèn)題19642中所建議抱冷,更改語(yǔ)言以允許_在分配(包括return或函數(shù)調(diào)用)的右側(cè)使用往踢。
  • 問(wèn)題21182中所建議,更改語(yǔ)言以允許return ...返回結(jié)果類型的零值徘层。

我們認(rèn)為在決定確定哪種方法之前峻呕,還需要更多有關(guān)此設(shè)計(jì)的經(jīng)驗(yàn)。

很多惱人的括號(hào)

如果無(wú)法推斷出類型實(shí)參趣效,則使用類型實(shí)參調(diào)用函數(shù)需要附加的類型實(shí)參列表瘦癌。如果函數(shù)返回一個(gè)函數(shù),并且我們調(diào)用它跷敬,則會(huì)得到更多的括號(hào)讯私。

    F(int, float64)(x, y)(s)

我們嘗試了其他語(yǔ)法,例如使用冒號(hào)將類型參數(shù)與常規(guī)參數(shù)分開西傀。在我們看來(lái)斤寇,當(dāng)前的設(shè)計(jì)是最好的,但可能會(huì)有更好的選擇拥褂。

定義復(fù)合類型

正如上面所討論的娘锁,當(dāng)一個(gè)額外的類型參數(shù)是必需的,其基礎(chǔ)類型是復(fù)合類型饺鹃,作為結(jié)果返回相同的是定義的類型莫秆。

例如间雀,此函數(shù)將在切片上映射一個(gè)函數(shù)。

// Map applies f to each element of s, returning a new slice
// holding the results.
func Map(type T)(s []T, f func(T) T) []T {
    r := make([]T, len(s))
    for i, v := range s {
        r[i] = f(v)
    }
    return r
}

但是镊屎,在調(diào)用已定義類型時(shí)惹挟,它將返回該類型的元素類型的切片,而不是已定義類型本身缝驳。

// MySlice is a defined type.
type MySlice []int

// DoubleMySlice returns a new MySlice whose elements are twice
// that of the corresponding elements of s.
func DoubleMySlice(s MySlice) MySlice {
    s2 := Map(s, func(e int) int { return 2 * e })
    // Here s2 is type []int, not type MySlice.
    return MySlice(s2)
}

正如上面所討論的连锯,這可以通過(guò)使用一個(gè)額外的類型參數(shù)Map避免,并且使用描述切片和元件類型之間的關(guān)系所需的約束條件用狱。這種方式可行运怖,但很尷尬。

識(shí)別匹配的預(yù)聲明類型

該設(shè)計(jì)沒(méi)有提供任何方法來(lái)測(cè)試與類型實(shí)參匹配的基礎(chǔ)類型齿拂。代碼可以通過(guò)轉(zhuǎn)換為空接口類型并使用類型斷言或類型開關(guān)的某種笨拙方法來(lái)測(cè)試實(shí)際的類型參數(shù)。這可以讓代碼測(cè)試實(shí)際的與基礎(chǔ)類型不同的類型參數(shù)肴敛。

下面是一個(gè)顯示差異的示例署海。

type Float interface {
    type float32, float64
}

func NewtonSqrt(type T Float)(v T) T {
    var iterations int
    switch (interface{})(v).(type) {
    case float32:
        iterations = 4
    case float64:
        iterations = 5
    default:
        panic(fmt.Sprintf("unexpected type %T", v))
    }
    // Code omitted.
}

type MyFloat float32

var G = NewtonSqrt(MyFloat(64))

在初始化G時(shí),該代碼就會(huì)拋異常医男,因?yàn)樵?code>NewtonSqrt中的vMyFloat砸狞,而不是float32float64。該函數(shù)實(shí)際要測(cè)試的不是的類型v镀梭,而是v約束中匹配的類型刀森。

處理此問(wèn)題的一種方法是允許在類型T上進(jìn)行類型切換,條件是該類型T將始終與約束中定義的類型匹配报账。僅當(dāng)約束條件列出顯式類型時(shí)才允許這種類型的切換研底,并且僅允許約束條件中列出的類型作為情況使用。

無(wú)法表達(dá)可轉(zhuǎn)換性

該設(shè)計(jì)無(wú)法表達(dá)兩個(gè)不同類型參數(shù)之間的可轉(zhuǎn)換性透罢。例如榜晦,無(wú)法編寫此函數(shù):

// Copy copies values from src to dst, converting them as they go.
// It returns the number of items copied, which is the minimum of
// the lengths of dst and src.
// This implementation is INVALID.
func Copy(type T1, T2)(dst []T1, src []T2) int {
    for i, x := range src {
        if i > len(dst) {
            return i
        }
        dst[i] = T1(x) // INVALID
    }
    return len(src)
}

從類型T2到類型的轉(zhuǎn)換T1是無(wú)效的,因?yàn)槿魏我环N類型都沒(méi)有允許轉(zhuǎn)換的約束羽圃。更糟糕的是乾胶,通常沒(méi)有辦法編寫這樣的約束。在T1T2都可能需要某種類型列表的特殊情況下朽寞,可以像前面討論的使用類型列表進(jìn)行類型轉(zhuǎn)換時(shí)所述那樣編寫此函數(shù)识窿。但是例如,T1是接口類型脑融,T2實(shí)現(xiàn)了該接口的類型喻频,這類約束無(wú)法編寫。

值得注意的是肘迎,如果T1是接口類型半抱,則可以使用對(duì)空接口類型的轉(zhuǎn)換和類型斷言來(lái)編寫泥耀,但這當(dāng)然不是編譯時(shí)類型安全的。

// Copy copies values from src to dst, converting them as they go.
// It returns the number of items copied, which is the minimum of
// the lengths of dst and src.
func Copy(type T1, T2)(dst []T1, src []T2) int {
    for i, x := range src {
        if i > len(dst) {
            return i
        }
        dst[i] = (interface{})(x).(T1)
    }
    return len(src)
}
沒(méi)有參數(shù)化方法

本設(shè)計(jì)草案不允許方法聲明特定于該方法的類型參數(shù)躲履。接收器可能具有類型參數(shù)鸽心,但是該方法不能添加任何類型參數(shù)。

在Go中史简,方法的主要作用之一是允許類型實(shí)現(xiàn)接口乃秀。尚不清楚是否有可能允許參數(shù)化方法實(shí)現(xiàn)接口。例如圆兵,考慮以下代碼跺讯。此代碼使用了多個(gè)程序包使問(wèn)題更清楚。

package p1

// S is a type with a parameterized method Identity.
type S struct{}

// Identity is a simple identity method that works for any type.
func (S) Identity(type T)(v T) T { return v }

package p2

// HasIdentity is an interface that matches any type with a
// parameterized Identity method.
type HasIdentity interface {
    Identity(type T)(T) T
}

package p3

import "p2"

// CheckIdentity checks the Identity method if it exists.
// Note that although this function calls a parameterized method,
// this function is not itself parameterized.
func CheckIdentity(v interface{}) {
    if vi, ok := v.(p2.HasIdentity); ok {
        if got := vi.Identity(int)(0); got != 0 {
            panic(got)
        }
    }
}

package p4

import (
    "p1"
    "p3"
)

// CheckSIdentity passes an S value to CheckIdentity.
func CheckSIdentity() {
    p3.CheckIdentity(p1.S{})
}

在此示例中殉农,我們有一個(gè)帶有參數(shù)化方法的S類型刀脏,也有一個(gè)帶有參數(shù)化方法的HasIdentityS實(shí)現(xiàn)了HasIdentity超凳。因此愈污,函數(shù)p3.CheckIdentity可以使用int參數(shù)調(diào)用vi.Identity,在此示例中為參數(shù)S.Identity(int)轮傍。但是包p3對(duì)類型p1.S一無(wú)所知暂雹。程序中可能沒(méi)有其他地方調(diào)用S.Identity。我們需要實(shí)例化S.Identity(int)创夜,但是如何做到杭跪?

我們可以在鏈接器時(shí)期實(shí)例化它,但是在一般情況下驰吓,它要求鏈接器遍歷程序的完整調(diào)用圖以確定可能傳遞給CheckIdentity的類型集涧尿。甚至在涉及類型反射的一般情況下,這種遍歷也是不夠的檬贰,因?yàn)榉瓷淇赡軙?huì)根據(jù)用戶輸入的字符串查找方法现斋。因此,通常在鏈接器中實(shí)例化參數(shù)化方法可能需要為每個(gè)可能的類型實(shí)參實(shí)例化每個(gè)參數(shù)化方法偎蘸,這是不可行的庄蹋。

或者,我們可以在它的運(yùn)行時(shí)間實(shí)例化迷雪。通常限书,這意味著使用某種JIT,或編譯代碼以使用某種基于反射的方法章咧。每種方法的實(shí)施都非常復(fù)雜倦西,并且在運(yùn)行時(shí)會(huì)很慢。

或者赁严,我們可以確定參數(shù)化的方法實(shí)際上不實(shí)現(xiàn)接口扰柠,但是我們?yōu)槭裁葱枰椒ň筒磺宄朔垲怼H绻覀兒雎越涌冢瑒t任何參數(shù)化方法都可以實(shí)現(xiàn)為參數(shù)化函數(shù)卤档。

因此蝙泼,盡管乍看之下參數(shù)化方法似乎很有用,但我們必須確定它們的含義以及如何實(shí)現(xiàn)劝枣。

廢棄的想法

這種設(shè)計(jì)并不完美汤踏,隨著我們積累經(jīng)驗(yàn),它將進(jìn)一步完善舔腾。也就是說(shuō)溪胶,我們已經(jīng)詳細(xì)考慮了許多想法。本節(jié)列出了其中的一些想法稳诚,希望這將有助于減少重復(fù)的討論哗脖。這些想法以常見問(wèn)題解答的形式列出。

協(xié)議怎么樣了扳还?

較早的泛型設(shè)計(jì)草案使用新語(yǔ)言構(gòu)造實(shí)現(xiàn)了約束才避,稱之為協(xié)議(contracts)。類型列表僅出現(xiàn)在協(xié)議中普办,而不出現(xiàn)在接口類型上工扎。但是徘钥,許多人很難理解協(xié)議和接口類型之間的區(qū)別衔蹲。事實(shí)證明,協(xié)議可以表示為一組相應(yīng)的接口呈础。因此舆驶,沒(méi)有協(xié)議并不會(huì)減弱表達(dá)能力。我們決定簡(jiǎn)化為僅使用接口類型的方法而钞。

為什么不使用方法而是類型列表沙廉?

類型列表很奇怪。 為什么不為所有運(yùn)算符編寫方法臼节?

可以允許使用運(yùn)算符作為方法名稱撬陵,從而導(dǎo)致諸如+(T) T的方法。不幸的是网缝,這還不夠巨税。我們需要某種機(jī)制來(lái)描述與任何整數(shù)類型匹配的類型,以便進(jìn)行不限于單個(gè)int類型的操作(例如粉臊,移位<<(integer) T和索引編制[](integer) T)草添。對(duì)于諸如之類的操作,我們需要一個(gè)無(wú)類型的布爾類型比如==(T) untyped bool扼仲。我們需要為諸如轉(zhuǎn)換之類的操作引入新的符號(hào)远寸,或者表達(dá)一個(gè)可能在一種類型上變化的范圍抄淑,這可能需要一些新的語(yǔ)法。我們需要某種機(jī)制來(lái)描述無(wú)類型常量的有效值驰后。我們將不得不考慮是否支持< (T) bool肆资,泛型函數(shù)也可以使用它<=,并且類似地倡怎,是否支持+(T) T表示函數(shù)也可以使用++迅耘。使這種方法可行也許是可行的,但并非一帆風(fēng)順监署。此設(shè)計(jì)中使用的方法似乎更簡(jiǎn)單颤专,并且僅依賴于一個(gè)新的語(yǔ)法構(gòu)造(類型列表)和一個(gè)新的名稱(comparable)。

為什么不將類型參數(shù)封裝成包钠乏?

我們對(duì)此進(jìn)行了廣泛的調(diào)查栖秕。當(dāng)你要編寫一個(gè)list程序包,并且希望該程序包包含一個(gè)Transform函數(shù)將List一個(gè)元素類型轉(zhuǎn)換為另一個(gè)List元素類型時(shí)晓避,這將成為問(wèn)題簇捍。包的一個(gè)實(shí)例化中的函數(shù)返回需要同一包的不同實(shí)例化的類型是非常尷尬的。

它還使包邊界與類型定義混淆俏拱。沒(méi)有特別的理由認(rèn)為泛型類型的使用會(huì)整齊地分解為包暑塑。有時(shí)他們會(huì),有時(shí)他們不會(huì)锅必。

為什么不使用的語(yǔ)法F<T>類似于C ++和Java事格?

在解析函數(shù)(例如)v := F<T>,看到的<是不確定的搞隐,不知道我們看到的是類型實(shí)例化還是使用<運(yùn)算符的表達(dá)式驹愚。解決這個(gè)問(wèn)題需要有效的無(wú)限制的預(yù)解析。我們希望努力保持Go解析器的效率劣纲。

為什么不使用語(yǔ)法F[T]逢捺?

解析類型聲明時(shí)type A [T] int,這是一個(gè)定義為泛型類型int還是帶有T元素的數(shù)組類型癞季,這是不確定的劫瞳。但是,這可以通過(guò)type A [type T] int泛型來(lái)解決绷柒。

解析聲明func f(A[T]int)(如類型的單個(gè)參數(shù)[T]int)和func f(A[T], int)(兩個(gè)參數(shù)志于,一個(gè)類型A[T]和一個(gè)類型int)的解析聲明表明,需要進(jìn)行一些額外的預(yù)解析辉巡。這是可解決的恨憎,但增加了解析的復(fù)雜性。

該語(yǔ)言通常在逗號(hào)分隔的列表中允許尾隨逗號(hào),因此對(duì)于A[T,]如果A是泛型憔恳,則應(yīng)允許使用瓤荔,但對(duì)于索引表達(dá)式通常不允許。但是钥组,解析器無(wú)法知道A是泛型類型還是slice输硝,array或map類型的值,因此只有在類型檢查完成后才能報(bào)告此解析錯(cuò)誤程梦。同樣点把,可解決但復(fù)雜。

更普遍的認(rèn)知是屿附,我們覺(jué)得方括號(hào)在頁(yè)面上有點(diǎn)煩郎逃,括號(hào)更像Go。隨著我們獲得更多經(jīng)驗(yàn)挺份,我們未來(lái)會(huì)重新評(píng)估該決定褒翰。

為什么不使用F?T?图柏?

我們考慮過(guò)了耻涛,但是我們無(wú)法要求使用非ASCII。

為什么不在內(nèi)置包中定義約束班缎?

不用寫出類型列表各聘,而使用像* constraints.Arithmeticconstraints.Comparable*這樣的名稱揣非。

列出所有可能的類型組合非常冗長(zhǎng)。它還引入了一組新名稱躲因,不僅是代碼的作者早敬,讀者也必須記住。此設(shè)計(jì)的驅(qū)動(dòng)目標(biāo)之一是引入盡可能少的新名稱毛仪。在此設(shè)計(jì)中搁嗓,我們僅引入一個(gè)新的預(yù)聲明的名稱芯勘。

我們希望箱靴,如果人們發(fā)現(xiàn)這樣的名稱有用,我們可以引入一個(gè)constraints包荷愕,該包以約束的形式定義有用的名稱衡怀,這些約束可以由其他類型和函數(shù)使用并嵌入其他約束中。這將在標(biāo)準(zhǔn)庫(kù)中定義安疗,同時(shí)使程序員可以靈活地在適當(dāng)?shù)牡胤绞褂闷渌愋偷慕M合抛杨。

為什么不允許在類型為類型參數(shù)的值上使用類型聲明?

在此設(shè)計(jì)的早期版本中荐类,我們?cè)试S對(duì)類型為類型參數(shù)或基于類型參數(shù)的變量使用類型聲明和類型判斷怖现。我們刪除了此功能,因?yàn)榭梢詫⑷魏晤愋偷闹缔D(zhuǎn)換為空接口類型,然后在其上使用類型斷言或類型判斷屈嗤。同樣潘拨,有時(shí)會(huì)混淆,具有類型列表的約束中類型斷言饶号,還是類型切換將使用實(shí)際的類型實(shí)參而不是類型實(shí)參的基礎(chǔ)類型铁追。

與Java比較

關(guān)于Java泛型的大多數(shù)抱怨都圍繞類型擦除。此設(shè)計(jì)沒(méi)有類型擦除茫船。泛型類型的反射信息將包括完整的編譯時(shí)類型信息琅束。

在Java類型中,通配符(List<? extends Number>算谈,List<? super Number>)實(shí)現(xiàn)協(xié)變和逆變涩禀。Go缺少這些概念,這使得泛型類型更加簡(jiǎn)單然眼。

與C ++的比較

C ++模板不對(duì)類型參數(shù)施加任何約束(除非采用了概念上的建議)埋泵。這意味著更改模板代碼可能會(huì)意外破壞某處的實(shí)例。這也意味著僅在實(shí)例化時(shí)報(bào)告異常罪治,并且可能嵌套得很深并且難以理解丽声。這種設(shè)計(jì)通過(guò)明確的要求約束避免了這些問(wèn)題。

C ++支持模板元編程觉义,可以理解為為在編譯時(shí)使用與非模板C ++完全不同的語(yǔ)法進(jìn)行的普通編程雁社。我們的設(shè)計(jì)沒(méi)有類似功能,這樣可以降低大量的復(fù)雜性晒骇。

C ++使用兩階段名稱查找霉撵,其中一些名稱是在模板定義的上下文中查找的,而某些名稱是在模板實(shí)例化的上下文中查找的洪囤。在這種設(shè)計(jì)中徒坡,所有名稱都在編寫時(shí)進(jìn)行查找。

實(shí)際上瘤缩,所有C ++編譯器都在實(shí)例化模板時(shí)編譯每個(gè)模板喇完。這會(huì)減慢編譯時(shí)間。這種設(shè)計(jì)為如何處理泛型函數(shù)的編譯提供了靈活性剥啤。

與Rust比較

我們的設(shè)計(jì)中描述的泛型類似于Rust中的泛型锦溪。

一個(gè)區(qū)別是,在Rust中府怯,必須明確定義特征綁定和類型之間的關(guān)聯(lián)刻诊。用Go術(shù)語(yǔ),這意味著我們將不得不在某個(gè)地方聲明類型是否滿足約束牺丙。正如Go類型可以在沒(méi)有顯式聲明的情況下滿足Go接口一樣则涯,在本設(shè)計(jì)中,Go類型的參數(shù)可以在沒(méi)有顯式聲明的情況下滿足約束。

在這種設(shè)計(jì)使用類型列表的地方粟判,Rust標(biāo)準(zhǔn)庫(kù)為比較之類的操作定義了標(biāo)準(zhǔn)特征肖揣。這些標(biāo)準(zhǔn)特征由Rust的原始類型自動(dòng)實(shí)現(xiàn),也可以由用戶定義的類型實(shí)現(xiàn)浮入。Rust提供了相當(dāng)廣泛的特征列表龙优,至少包含34個(gè),涵蓋了所有運(yùn)算符事秀。

Rust支持方法上的類型參數(shù)彤断,而本設(shè)計(jì)不支持。

例子

以下各節(jié)是如何使用此設(shè)計(jì)的示例易迹。這旨在解決由于Go缺乏泛型而出現(xiàn)的特殊場(chǎng)景宰衙。

Map/Reduce/Filter

這是一個(gè)如何為切片編寫map, reduce, 和 filter 功能的示例。這些功能希望能夠提供在Lisp睹欲,Python供炼,Java等中的類似的功能。

// Package slices implements various slice algorithms.
package slices

// Map turns a []T1 to a []T2 using a mapping function.
// This function has two type parameters, T1 and T2.
// There are no constraints on the type parameters,
// so this works with slices of any type.
func Map(type T1, T2)(s []T1, f func(T1) T2) []T2 {
    r := make([]T2, len(s))
    for i, v := range s {
        r[i] = f(v)
    }
    return r
}

// Reduce reduces a []T1 to a single value using a reduction function.
func Reduce(type T1, T2)(s []T1, initializer T2, f func(T2, T1) T2) T2 {
    r := initializer
    for _, v := range s {
        r = f(r, v)
    }
    return r
}

// Filter filters values from a slice using a filter function.
// It returns a new slice with only the elements of s
// for which f returned true.
func Filter(type T)(s []T, f func(T) bool) []T {
    var r []T
    for _, v := range s {
        if f(v) {
            r = append(r, v)
        }
    }
    return r
}

這是這些函數(shù)的一些調(diào)用示例窘疮。根據(jù)非類型參數(shù)的類型推斷類型參數(shù)袋哼。

    s := []int{1, 2, 3}

    floats := slices.Map(s, func(i int) float64 { return float64(i) })
    // Now floats is []float64{1.0, 2.0, 3.0}.

    sum := slices.Reduce(s, 0, func(i, j int) int { return i + j })
    // Now sum is 6.

    evens := slices.Filter(s, func(i int) bool { return i%2 == 0 })
    // Now evens is []int{2}.

Map keys

這是獲取任何map鍵值的方法。

// Package maps provides general functions that work for all map types.
package maps

// Keys returns the keys of the map m in a slice.
// The keys will be returned in an unpredictable order.
// This function has two type parameters, K and V.
// Map keys must be comparable, so key has the predeclared
// constraint comparable. Map values can be any type;
// the empty interface type imposes no constraints.
func Keys(type K comparable, V interface{})(m map[K]V) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

在典型使用中闸衫,將推斷出映射key和val類型涛贯。

    k := maps.Keys(map[int]int{1:2, 2:4})
    // Now k is either []int{1, 2} or []int{2, 1}.

Sets

許多人要求擴(kuò)展或簡(jiǎn)化Go的內(nèi)置map類型以支持集合類型。下面的set類型是基于類型安全的實(shí)現(xiàn)蔚出,盡管它使用的是方法而不是類似的運(yùn)算符[]弟翘。

// Package set implements sets of any comparable type.
package set

// Set is a set of values.
type Set(type T comparable) map[T]struct{}

// Make returns a set of some element type.
func Make(type T comparable)() Set(T) {
    return make(Set(T))
}

// Add adds v to the set s.
// If v is already in s this has no effect.
func (s Set(T)) Add(v T) {
    s[v] = struct{}{}
}

// Delete removes v from the set s.
// If v is not in s this has no effect.
func (s Set(T)) Delete(v T) {
    delete(s, v)
}

// Contains reports whether v is in s.
func (s Set(T)) Contains(v T) bool {
    _, ok := s[v]
    return ok
}

// Len reports the number of elements in s.
func (s Set(T)) Len() int {
    return len(s)
}

// Iterate invokes f on each element of s.
// It's OK for f to call the Delete method.
func (s Set(T)) Iterate(f func(T)) {
    for v := range s {
        f(v)
    }
}

示例:

    // Create a set of ints.
    // We pass (int) as a type argument.
    // Then we write () because Make does not take any non-type arguments.
    // We have to pass an explicit type argument to Make.
    // Type inference doesn't work because the type argument
    // to Make is only used for a result parameter type.
    s := set.Make(int)()

    // Add the value 1 to the set s.
    s.Add(1)

    // Check that s does not contain the value 2.
    if s.Contains(2) { panic("unexpected 2") }

本示例說(shuō)明如何使用此設(shè)計(jì)為現(xiàn)有API提供編譯時(shí)類型安全的包裝器。

sort

在引入sort.Slice之前骄酗,一個(gè)普遍的抱怨是需要使用樣板定義才能使用sort.Sort稀余。通過(guò)這種設(shè)計(jì),我們可以如下所示添加到sort包中趋翻,:

// Ordered is a type constraint that matches all ordered types.
// (An ordered type is one that supports the < <= >= > operators.)
// In practice this type constraint would likely be defined in
// a standard library package.
type Ordered interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

// orderedSlice is an internal type that implements sort.Interface.
// The Less method uses the < operator. The Ordered type constraint
// ensures that T has a < operator.
type orderedSlice(type T Ordered) []T

func (s orderedSlice(T)) Len() int           { return len(s) }
func (s orderedSlice(T)) Less(i, j int) bool { return s[i] < s[j] }
func (s orderedSlice(T)) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// OrderedSlice sorts the slice s in ascending order.
// The elements of s must be ordered using the < operator.
func OrderedSlice(type T Ordered)(s []T) {
    // Convert s to the type orderedSlice(T).
    // As s is []T, and orderedSlice(T) is defined as []T,
    // this conversion is permitted.
    // orderedSlice(T) implements sort.Interface,
    // so can pass the result to sort.Sort.
    // The elements will be sorted using the < operator.
    sort.Sort(orderedSlice(T)(s))
}

現(xiàn)在我們可以寫:

    s1 := []int32{3, 5, 2}
    sort.OrderedSlice(s1)
    // Now s1 is []int32{2, 3, 5}

    s2 := []string{"a", "c", "b"})
    sort.OrderedSlice(s2)
    // Now s2 is []string{"a", "b", "c"}

同樣睛琳,我們可以添加一個(gè)使用比較函數(shù)進(jìn)行排序的函數(shù),類似于sort.Slice但該函數(shù)偏向獲取值而不是切片索引嘿歌。

// sliceFn is an internal type that implements sort.Interface.
// The Less method calls the cmp field.
type sliceFn(type T) struct {
    s   []T
    cmp func(T, T) bool
}

func (s sliceFn(T)) Len() int           { return len(s.s) }
func (s sliceFn(T)) Less(i, j int) bool { return s.cmp(s.s[i], s.s[j]) }
func (s sliceFn(T)) Swap(i, j int)      { s.s[i], s.s[j] = s.s[j], s.s[i] }

// SliceFn sorts the slice s according to the function cmp.
func SliceFn(type T)(s []T, cmp func(T, T) bool) {
    Sort(sliceFn(E){s, cmp})
}

調(diào)用此示例如下:

var s []*Person
    // ...
    sort.SliceFn(s, func(p1, p2 *Person) bool { return p1.Name < p2.Name })

Channels

從未編寫過(guò)許多簡(jiǎn)單的泛型通道函數(shù)掸掏,因?yàn)樗鼈儽仨毷褂梅瓷鋪?lái)編寫茁影,并且調(diào)用者必須鍵入斷言結(jié)果宙帝。通過(guò)這種設(shè)計(jì),它們變得易于編寫募闲。

// Package chans implements various channel algorithms.
package chans

import "runtime"

// Ranger provides a convenient way to exit a goroutine sending values
// when the receiver stops reading them.
//
// Ranger returns a Sender and a Receiver. The Receiver provides a
// Next method to retrieve values. The Sender provides a Send method
// to send values and a Close method to stop sending values. The Next
// method indicates when the Sender has been closed, and the Send
// method indicates when the Receiver has been freed.
func Ranger(type T)() (*Sender(T), *Receiver(T)) {
    c := make(chan T)
    d := make(chan bool)
    s := &Sender(T){values: c, done: d}
    r := &Receiver(T){values: c, done: d}
    // The finalizer on the receiver will tell the sender
    // if the receiver stops listening.
    runtime.SetFinalizer(r, r.finalize)
    return s, r
}

// A Sender is used to send values to a Receiver.
type Sender(type T) struct {
    values chan<- T
    done   <-chan bool
}

// Send sends a value to the receiver. It reports whether any more
// values may be sent; if it returns false the value was not sent.
func (s *Sender(T)) Send(v T) bool {
    select {
    case s.values <- v:
        return true
    case <-s.done:
        // The receiver has stopped listening.
        return false
    }
}

// Close tells the receiver that no more values will arrive.
// After Close is called, the Sender may no longer be used.
func (s *Sender(T)) Close() {
    close(s.values)
}

// A Receiver receives values from a Sender.
type Receiver(type T) struct {
    values <-chan T
    done  chan<- bool
}

// Next returns the next value from the channel. The bool result
// reports whether the value is valid. If the value is not valid, the
// Sender has been closed and no more values will be received.
func (r *Receiver(T)) Next() (T, bool) {
    v, ok := <-r.values
    return v, ok
}

// finalize is a finalizer for the receiver.
// It tells the sender that the receiver has stopped listening.
func (r *Receiver(T)) finalize() {
    close(r.done)
}

下一節(jié)將提供使用此功能的示例步脓。

容器

Go中對(duì)泛型的常見要求之一是能夠編寫編譯時(shí)類型安全的容器。這種設(shè)計(jì)使為現(xiàn)有容器編寫編譯時(shí)類型安全的包裝器變得容易。我們不會(huì)為此寫一個(gè)例子靴患。這種設(shè)計(jì)還使編寫不使用裝箱的編譯時(shí)類型安全的容器變得容易仍侥。

這是實(shí)現(xiàn)為二叉樹的有序映射的示例。它如何工作的細(xì)節(jié)不是太重要鸳君。要點(diǎn)是下面兩點(diǎn):

  • 該代碼以Go風(fēng)格編寫农渊,并在需要時(shí)使用鍵和值類型。
  • 鍵和值直接存儲(chǔ)在樹的節(jié)點(diǎn)中或颊,不使用指針砸紊,也不作為接口值裝箱。
// Package orderedmap provides an ordered map, implemented as a binary tree.
package orderedmap

import "chans"

// Map is an ordered map.
type Map(type K, V) struct {
    root    *node(K, V)
    compare func(K, K) int
}

// node is the type of a node in the binary tree.
type node(type K, V) struct {
    k           K
    v           V
    left, right *node(K, V)
}

// New returns a new map.
// Since the type parameter V is only used for the result,
// type inference does not work, and calls to New must always
// pass explicit type arguments.
func New(type K, V)(compare func(K, K) int) *Map(K, V) {
    return &Map(K, V){compare: compare}
}

// find looks up k in the map, and returns either a pointer
// to the node holding k, or a pointer to the location where
// such a node would go.
func (m *Map(K, V)) find(k K) **node(K, V) {
    pn := &m.root
    for *pn != nil {
        switch cmp := m.compare(k, (*pn).k); {
        case cmp < 0:
            pn = &(*pn).left
        case cmp > 0:
            pn = &(*pn).right
        default:
            return pn
        }
    }
    return pn
}

// Insert inserts a new key/value into the map.
// If the key is already present, the value is replaced.
// Reports whether this is a new key.
func (m *Map(K, V)) Insert(k K, v V) bool {
    pn := m.find(k)
    if *pn != nil {
        (*pn).v = v
        return false
    }
    *pn = &node(K, V){k: k, v: v}
    return true
}

// Find returns the value associated with a key, or zero if not present.
// The bool result reports whether the key was found.
func (m *Map(K, V)) Find(k K) (V, bool) {
    pn := m.find(k)
    if *pn == nil {
        var zero V // see the discussion of zero values, above
        return zero, false
    }
    return (*pn).v, true
}

// keyValue is a pair of key and value used when iterating.
type keyValue(type K, V) struct {
    k K
    v V
}

// InOrder returns an iterator that does an in-order traversal of the map.
func (m *Map(K, V)) InOrder() *Iterator(K, V) {
    type kv = keyValue(K, V) // convenient shorthand
    sender, receiver := chans.Ranger(kv)()
    var f func(*node(K, V)) bool
    f = func(n *node(K, V)) bool {
        if n == nil {
            return true
        }
        // Stop sending values if sender.Send returns false,
        // meaning that nothing is listening at the receiver end.
        return f(n.left) &&
            sender.Send(kv{n.k, n.v}) &&
            f(n.right)
    }
    go func() {
        f(m.root)
        sender.Close()
    }()
    return &Iterator{receiver}
}

// Iterator is used to iterate over the map.
type Iterator(type K, V) struct {
    r *chans.Receiver(keyValue(K, V))
}

// Next returns the next key and value pair. The bool result reports
// whether the values are valid. If the values are not valid, we have
// reached the end.
func (it *Iterator(K, V)) Next() (K, V, bool) {
    kv, ok := it.r.Next()
    return kv.k, kv.v, ok
}

這個(gè)包的使用效果如下:

import "container/orderedmap"

// Set m to an ordered map from string to string,
// using strings.Compare as the comparison function.
var m = orderedmap.New(string, string)(strings.Compare)

// Add adds the pair a, b to m.
func Add(a, b string) {
    m.Insert(a, b)
}

append

存在預(yù)先聲明的append功能來(lái)替換樣板囱挑,否則需要增加切片醉顽。在append添加之前,bytes包中有一個(gè)Add函數(shù):

// Add appends the contents of t to the end of s and returns the result.
// If s has enough capacity, it is extended in place; otherwise a
// new array is allocated and returned.
func Add(s, t []byte) []byte

Add將兩個(gè)[]byte值附加在一起平挑,返回一個(gè)新的切片游添。這對(duì)于[]byte很好,但是如果你有其他類型的切片通熄,則必須編寫基本相同的代碼以附加更多值唆涝。如果那時(shí)可以使用這種設(shè)計(jì),也許我們不會(huì)增加append唇辨。相反石抡,我們可以這樣寫:

// Package slices implements various slice algorithms.
package slices

// Append appends the contents of t to the end of s and returns the result.
// If s has enough capacity, it is extended in place; otherwise a
// new array is allocated and returned.
func Append(type T)(s []T, t ...T) []T {
    lens := len(s)
    tot := lens + len(t)
    if tot < 0 {
        panic("Append: cap out of range")
    }
    if tot > cap(s) {
        news := make([]T, tot, tot + tot/2)
        copy(news, s)
        s = news
    }
    s = s[:tot]
    copy(s[lens:], t)
    return s
}

該示例使用了預(yù)先聲明的copy函數(shù),但是沒(méi)關(guān)系助泽,我們也可以編寫該函數(shù):

// Copy copies values from t to s, stopping when either slice is
// full, returning the number of values copied.
func Copy(type T)(s, t []T) int {
    i := 0
    for ; i < len(s) && i < len(t); i++ {
        s[i] = t[i]
    }
    return i
}

這些功能可以按如下使用:

    s := slices.Append([]int{1, 2, 3}, 4, 5, 6)
    // Now s is []int{1, 2, 3, 4, 5, 6}.
    slices.Copy(s[3:], []int{7, 8, 9})
    // Now s is []int{1, 2, 3, 7, 8, 9}

這段代碼并未實(shí)現(xiàn)將string追加或復(fù)制到[]byte的特殊情況啰扛,因此它的效率不如預(yù)定義函數(shù)的實(shí)現(xiàn)那樣高效。該示例仍然表明嗡贺,使用此設(shè)計(jì)將允許appendcopy使用泛型的方式編寫一次隐解,而無(wú)需任何其他特殊語(yǔ)言功能。

Metrics

Go體驗(yàn)報(bào)告 中Sameer Ajmani描述了一種Metrics實(shí)現(xiàn)诫睬。每個(gè)指標(biāo)都有一個(gè)值和一個(gè)或多個(gè)字段煞茫。字段具有不同的類型。定義指標(biāo)需要指定字段的類型摄凡,并使用Add方法創(chuàng)建一個(gè)值朋其。Add方法將字段類型作為參數(shù)惯驼,并記錄該字段集的實(shí)例。C ++實(shí)現(xiàn)使用可變參數(shù)模板。Java實(shí)現(xiàn)包括類型名稱中的字段數(shù)体斩。C ++和Java實(shí)現(xiàn)都提供了編譯時(shí)類型安全的Add方法汇鞭。

下面是如何使用此設(shè)計(jì)在Go中實(shí)現(xiàn)編譯時(shí)類型安全的類似的Add方法婆廊。因?yàn)椴恢С挚勺償?shù)量的類型參數(shù),所以對(duì)于像Java中的不同數(shù)量的參數(shù)其弊,我們必須使用不同的名稱。此實(shí)現(xiàn)僅適用于可比較的類型膀斋。更復(fù)雜的實(shí)現(xiàn)可以接受比較函數(shù)以使用任意類型梭伐。

// Package metrics provides a general mechanism for accumulating
// metrics of different values.
package metrics

import "sync"

// Metric1 accumulates metrics of a single value.
type Metric1(type T comparable) struct {
    mu sync.Mutex
    m  map[T]int
}

// Add adds an instance of a value.
func (m *Metric1(T)) Add(v T) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[T]int)
    }
    m.m[v]++
}

// key2 is an internal type used by Metric2.
type key2(type T1, T2 comparable) struct {
    f1 T1
    f2 T2
}

// Metric2 accumulates metrics of pairs of values.
type Metric2(type T1, T2 comparable) struct {
    mu sync.Mutex
    m  map[key2(T1, T2)]int
}

// Add adds an instance of a value pair.
func (m *Metric2(T1, T2)) Add(v1 T1, v2 T2) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[key2(T1, T2)]int)
    }
    m.m[key2(T1, T2){v1, v2}]++
}

// key3 is an internal type used by Metric3.
type key3(type T1, T2, T3 comparable) struct {
    f1 T1
    f2 T2
    f3 T3
}

// Metric3 accumulates metrics of triples of values.
type Metric3(type T1, T2, T3 comparable) struct {
    mu sync.Mutex
    m  map[key3(T1, T2, T3)]int
}

// Add adds an instance of a value triplet.
func (m *Metric3(T1, T2, T3)) Add(v1 T1, v2 T2, v3 T3) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[key3(T1, T2, T3)]int)
    }
    m.m[key3(T1, T2, T3){v1, v2, v3}]++
}

// Repeat for the maximum number of permitted arguments.

使用此程序包如下所示:

import "metrics"

var m = metrics.Metric2(string, int){}

func F(s string, i int) {
    m.Add(s, i) // this call is type checked at compile time
}

由于缺少對(duì)可變參數(shù)類型參數(shù)的支持,此實(shí)現(xiàn)具有一定的重復(fù)性仰担。但是糊识,使用該軟件包很容易,而且類型安全摔蓝。

列表轉(zhuǎn)換

雖然切片是高效且易于使用的技掏,但在某些情況下,使用鏈表更合適项鬼。此示例主要顯示將一種類型的鏈表轉(zhuǎn)換為另一種類型哑梳,作為使用同一泛型類型的不同實(shí)例的示例。

// Package list provides a linked list of any type.
package list

// List is a linked list.
type List(type T) struct {
    head, tail *element(T)
}

// An element is an entry in a linked list.
type element(type T) struct {
    next *element(T)
    val  T
}

// Push pushes an element to the end of the list.
func (lst *List(T)) Push(v T) {
    if lst.tail == nil {
        lst.head = &element(T){val: v}
        lst.tail = lst.head
    } else {
        lst.tail.next = &element(T){val: v }
        lst.tail = lst.tail.next
    }
}

// Iterator ranges over a list.
type Iterator(type T) struct {
    next **element(T)
}

// Range returns an Iterator starting at the head of the list.
func (lst *List(T)) Range() *Iterator(T) {
    return Iterator(T){next: &lst.head}
}

// Next advances the iterator.
// It reports whether there are more elements.
func (it *Iterator(T)) Next() bool {
    if *it.next == nil {
        return false
    }
    it.next = &(*it.next).next
    return true
}

// Val returns the value of the current element.
// The bool result reports whether the value is valid.
func (it *Iterator(T)) Val() (T, bool) {
    if *it.next == nil {
        var zero T
        return zero, false
    }
    return (*it.next).val, true
}

// Transform runs a transform function on a list returning a new list.
func Transform(type T1, T2)(lst *List(T1), f func(T1) T2) *List(T2) {
    ret := &List(T2){}
    it := lst.Range()
    for {
        if v, ok := it.Val(); ok {
            ret.Push(f(v))
        }
        if !it.Next() {
            break
        }
    }
    return ret
}

點(diǎn)積

通用點(diǎn)積實(shí)現(xiàn)绘盟,適用于任何數(shù)字類型的切片鸠真。

// Numeric is a constraint that matches any numeric type.
// It would likely be in a constraints package in the standard library.
type Numeric interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        complex64, complex128
}

// DotProduct returns the dot product of two slices.
// This panics if the two slices are not the same length.
func DotProduct(type T Numeric)(s1, s2 []T) T {
    if len(s1) != len(s2) {
        panic("DotProduct: slices of unequal length")
    }
    var r T
    for i := range s1 {
        r += s1[i] * s2[i]
    }
    return r
}

(注意:泛型實(shí)現(xiàn)方法可能會(huì)影響DotProduct是否使用FMA,從而影響使用浮點(diǎn)類型時(shí)的確切結(jié)果龄毡。目前尚不清楚這是什么問(wèn)題吠卷,或者是否有任何方法可以解決。)

絕對(duì)差

通過(guò)使用Abs方法來(lái)計(jì)算兩個(gè)數(shù)值之間的絕對(duì)差沦零。這使用了上一個(gè)示例中定義的Numeric相同約束祭隔。

此示例使用了比用于計(jì)算絕對(duì)差的簡(jiǎn)單情況更多的組件。旨在說(shuō)明如何將算法的通用部分分解為使用方法的代碼路操,其中方法的確切定義可以根據(jù)所使用的類型的種類而變化疾渴。

// NumericAbs matches numeric types with an Abs method.
type NumericAbs(type T) interface {
    Numeric
    Abs() T
}

// AbsDifference computes the absolute value of the difference of
// a and b, where the absolute value is determined by the Abs method.
func AbsDifference(type T NumericAbs)(a, b T) T {
    d := a - b
    return d.Abs()
}

我們可以定義Abs適合于不同數(shù)字類型的方法。

// OrderedNumeric matches numeric types that support the < operator.
type OrderedNumeric interface {
    type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64
}

// Complex matches the two complex types, which do not have a < operator.
type Complex interface {
    type complex64, complex128
}

// OrderedAbs is a helper type that defines an Abs method for
// ordered numeric types.
type OrderedAbs(type T OrderedNumeric) T

func (a OrderedAbs(T)) Abs() OrderedAbs(T) {
    if a < 0 {
        return -a
    }
    return a
}

// ComplexAbs is a helper type that defines an Abs method for
// complex types.
type ComplexAbs(type T Complex) T

func (a ComplexAbs(T)) Abs() ComplexAbs(T) {
    d := math.Hypot(float64(real(a)), float64(imag(a)))
    return ComplexAbs(T)(complex(d, 0))
}

然后屯仗,我們可以通過(guò)與我們剛剛定義的類型之間進(jìn)行轉(zhuǎn)換來(lái)定義為調(diào)用者完成工作的函數(shù)搞坝。

// OrderedAbsDifference returns the absolute value of the difference
// between a and b, where a and b are of an ordered type.
func OrderedAbsDifference(type T OrderedNumeric)(a, b T) T {
    return T(AbsDifference(OrderedAbs(T)(a), OrderedAbs(T)(b)))
}

// ComplexAbsDifference returns the absolute value of the difference
// between a and b, where a and b are of a complex type.
func ComplexAbsDifference(type T Complex)(a, b T) T {
    return T(AbsDifference(ComplexAbs(T)(a), ComplexAbs(T)(b)))
}

值得注意的是,此設(shè)計(jì)的功能不足以編寫如下代碼:

// This function is INVALID.
func GeneralAbsDifference(type T Numeric)(a, b T) T {
    switch (interface{})(a).(type) {
    case int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64:
        return OrderedAbsDifference(a, b) // INVALID
    case complex64, complex128:
        return ComplexAbsDifference(a, b) // INVALID
    }
}

OrderedAbsDifferenceComplexAbsDifference的調(diào)用是無(wú)效的魁袜,因?yàn)椴皇撬袑?shí)現(xiàn)了Numeric約束的類型可以實(shí)現(xiàn)OrderedNumericComplex約束桩撮。盡管類型切換意味著該代碼在概念上將在運(yùn)行時(shí)運(yùn)行,但是不支持在編譯時(shí)解析此代碼峰弹。這是表達(dá)上述遺漏之一的另一種方式:此設(shè)計(jì)未提供定制化店量。

附錄

本附錄涵蓋了設(shè)計(jì)的各種細(xì)節(jié),這些細(xì)節(jié)似乎沒(méi)有足夠的重要性在前面的部分中進(jìn)行介紹鞠呈。

泛型別名

type別名可以引用泛型融师,但是type別名可能沒(méi)有自己的參數(shù)。之所以存在此限制粟按,是因?yàn)樯胁磺宄绾问褂镁哂屑s束的類型參數(shù)來(lái)處理類型別名诬滩。

type VectorAlias = Vector

在這種情況下霹粥,類型別名的使用將必須提供適合于被別名化的通用類型的類型參數(shù)灭将。

var v VectorAlias(int)

類型別名也可以指代實(shí)例化的類型疼鸟。

type VectorInt = Vector(int)

實(shí)例化函數(shù)

Go通常允許在不傳遞任何參數(shù)的情況下引用函數(shù),從而產(chǎn)生函數(shù)類型的值庙曙。你不能使用具有類型參數(shù)的函數(shù)來(lái)執(zhí)行此操作空镜;必須在編譯時(shí)知道所有類型的參數(shù)。就是說(shuō)捌朴,你可以通過(guò)傳遞類型實(shí)參來(lái)實(shí)例化該函數(shù)吴攒,但不必調(diào)用實(shí)例化。這將產(chǎn)生一個(gè)沒(méi)有類型參數(shù)的函數(shù)值砂蔽。

// PrintInts is type func([]int).
var PrintInts = Print(int)

嵌入式類型參數(shù)

當(dāng)泛型類型是結(jié)構(gòu)體洼怔,并且類型參數(shù)作為字段嵌入在結(jié)構(gòu)體中時(shí),該字段的名稱就是類型參數(shù)的名稱左驾。

// A Lockable is a value that may be safely simultaneously accessed
// from multiple goroutines via the Get and Set methods.
type Lockable(type T) struct {
    T
    mu sync.Mutex
}

// Get returns the value stored in a Lockable.
func (l *Lockable(T)) Get() T {
    l.mu.Lock()
    defer l.mu.Unlock()
    return l.T
}

// Set sets the value in a Lockable.
func (l *Lockable(T)) Set(v T) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.T = v
}

內(nèi)聯(lián)約束

正如我們?cè)?code>interface{}用作類型約束的示例中所看到的镣隶,約束沒(méi)有必要使用命名接口類型。類型參數(shù)列表可以使用接口類型诡右。

// Stringify calls the String method on each element of s,
// and returns the results.
func Stringify(type T interface { String() string })(s []T) (ret []string) {
    for _, v := range s {
        ret = append(ret, v.String())
    }
    return ret
}

復(fù)合類型推斷

我們現(xiàn)在不建議使用此功能安岂,但可以考慮該語(yǔ)言的未來(lái)版本。

我們也可以考慮為泛型類型復(fù)合支持類型推斷帆吻。

type Pair(type T) struct { f1, f2 T }
var V = Pair{1, 2} // inferred as Pair(int){1, 2}

目前尚不清楚這在實(shí)際代碼中出現(xiàn)的頻率域那。

泛型函數(shù)參數(shù)的類型推斷

我們現(xiàn)在不建議使用此功能,但可以考慮使用更高版本猜煮。

在下面的例子中次员,考慮在FindClose中調(diào)用Find。類型推斷可以確定類型參數(shù)FindT4王带,從我們知道的最后一個(gè)參數(shù)的類型必須是func(T4, T4) bool翠肘,從我們可以推斷出類型參數(shù)IsClose也必須T4。但是辫秧,前面介紹的類型推斷算法無(wú)法做到這一點(diǎn)束倍,因此我們必須顯式編寫IsClose(T4)

乍一看這似乎很深?yuàn)W盟戏,在將泛型函數(shù)傳遞給泛型Map和Filter函數(shù)時(shí)會(huì)出現(xiàn)這種情況绪妹。

// Differ has a Diff method that returns how different a value is.
type Differ(type T1) interface {
    Diff(T1) int
}

// IsClose returns whether a and b are close together, based on Diff.
func IsClose(type T2 Differ)(a, b T2) bool {
    return a.Diff(b) < 2
}

// Find returns the index of the first element in s that matches e,
// based on the cmp function. It returns -1 if no element matches.
func Find(type T3)(s []T3, e T3, cmp func(a, b T3) bool) int {
    for i, v := range s {
        if cmp(v, e) {
            return i
        }
    }
    return -1
}

// FindClose returns the index of the first element in s that is
// close to e, based on IsClose.
func FindClose(type T4 Differ)(s []T4, e T4) int {
    // With the current type inference algorithm we have to
    // explicitly write IsClose(T4) here, although it
    // is the only type argument we could possibly use.
    return Find(s, e, IsClose(T4))
}

類型參數(shù)的反射

盡管我們不建議更改反射包,但將來(lái)可能要考慮的一種可能性添加兩個(gè)新方法,reflect.Type:NumTypeArgument() int將類型實(shí)參的數(shù)量返回給類型柿究,TypeArgument(i) Type返回第i個(gè)類型實(shí)參邮旷。NumTypeArgument將為實(shí)例化的泛型類型返回非零值∮可以為reflect.Value定義類似的方法婶肩,對(duì)于NumTypeArgument實(shí)例化的泛型函數(shù)办陷,該方法將返回非零值÷杉撸可能會(huì)有某種程序會(huì)關(guān)心此信息民镜。

在類型字面量中實(shí)例化類型

在類型字面量的末尾實(shí)例化類型時(shí),存在解析歧義险毁。

x1 := []T(v1)
x2 := []T(v2){}

在此示例中制圈,第一種情況是將類型v1轉(zhuǎn)換為type []T。第二種情況是類型的復(fù)合字面量[]T(v2)畔况,其中T是我們使用type參數(shù)實(shí)例化的泛型類型v2鲸鹦。歧義是在我們看到開放括號(hào)的那一刻:解析器不知道它是在看到類型轉(zhuǎn)換還是類似復(fù)合字面量的東西。

為了避免這種歧義跷跪,我們需要在類型文字的末尾加上類型實(shí)例化馋嗜。要編寫作為類型實(shí)例化切片的類型字面量,必須編寫[](T(v1))吵瞻。如果沒(méi)有這些括號(hào)葛菇,[]T(x)則將解析為([]T)(x),而不是[](T(x))听皿。這僅適用于以類型名稱結(jié)尾的slice熟呛,array,map尉姨,chan和func類型字面量庵朝。

嵌入實(shí)例化的接口類型

將實(shí)例化的接口類型嵌入另一個(gè)接口類型時(shí),存在解析歧義又厉。

type I1(type T) interface {
    M(T)
}

type I2 interface {
    I1(int)
}

在此示例中九府,我們不知道interface I2是否具有一個(gè)名為I1,還是要嘗試將實(shí)例化類型I1(int)嵌入到I2中覆致。

為了向后兼容侄旬,我們將其視為前一種情況:I2具有名為I1的方法。

為了嵌入實(shí)例化的接口類型煌妈,我們要求使用額外的括號(hào)儡羔。

type I2 interface {
    (I1(int))
}

當(dāng)前語(yǔ)法不允許這樣做,這將放寬現(xiàn)有規(guī)則璧诵。

將實(shí)例化類型嵌入結(jié)構(gòu)中也是如此汰蜘。

type S1 struct {
    T(int) // field named T of type int
}

type S2 struct {
    (T(int)) // embedded field of type T(int)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市之宿,隨后出現(xiàn)的幾起案子族操,更是在濱河造成了極大的恐慌,老刑警劉巖比被,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件色难,死亡現(xiàn)場(chǎng)離奇詭異泼舱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)枷莉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門娇昙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人依沮,你說(shuō)我怎么就攤上這事涯贞∏箍瘢” “怎么了危喉?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)州疾。 經(jīng)常有香客問(wèn)我辜限,道長(zhǎng),這世上最難降的妖魔是什么严蓖? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任薄嫡,我火速辦了婚禮,結(jié)果婚禮上颗胡,老公的妹妹穿的比我還像新娘毫深。我一直安慰自己,他們只是感情好毒姨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布哑蔫。 她就那樣靜靜地躺著,像睡著了一般弧呐。 火紅的嫁衣襯著肌膚如雪闸迷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天俘枫,我揣著相機(jī)與錄音腥沽,去河邊找鬼。 笑死鸠蚪,一個(gè)胖子當(dāng)著我的面吹牛今阳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茅信,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼盾舌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了汹押?” 一聲冷哼從身側(cè)響起矿筝,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棚贾,沒(méi)想到半個(gè)月后窖维,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榆综,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年铸史,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鼻疮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琳轿,死狀恐怖判沟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情崭篡,我是刑警寧澤挪哄,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站琉闪,受9級(jí)特大地震影響躁愿,放射性物質(zhì)發(fā)生泄漏枫甲。R本人自食惡果不足惜葡秒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一肌索、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛀蜜,春花似錦刻两、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至壮池,卻和暖如春偏瓤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背椰憋。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工厅克, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人橙依。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓证舟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親窗骑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子女责,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348