Go泛型新方案 – 类型参数

今天Ian Lance Taylor和Robert Griesemer新推出一篇文章,介绍了Go泛型的新方案。两位都是Go核心开发组中的老大,也是负责Go泛型特性的负责人。

本文带你了解Go泛型的最新进展,更详细的介绍请看 The Next Step for Generics
Type Parameters – Draft Design
Summary of Go Generics Discussions
issue#15292

在最新版的泛型设计中,原来的 Contract
概念被抛弃了。本来,额外增加一个类似接口的 Contract
概念有点画蛇添足,而且增加语言和代码的复杂性,所以最新版直接使用接口代替。
当然,这个草案离正式的Go泛型方案还有很大的变数,下一步是Go语言改变的提案,顺利的话,Go泛型的支持会在明年的8月份的1.17版本中推出。
当然, Go开发组已经提供了一个在线编译运行当前泛型方案的工具,你可以编写泛型代码尝鲜:

package main

import (
    "fmt"
)

func Print(type T)(s []T) {
    for _, v := range s {
        fmt.Print(v)
    }
}

func main() {
    Print([]string{"Hello, ", "playground\n"})
}

如果你接触过泛型,你会很容易理解Go泛型的概念:

  • 函数可以有一个额外的泛型参数列表,使用 type
    关键字定义,比如 func F(type T)(p T) { ... }
  • 这些类型参数(type parameter)可以用在函数参数和方法体中
  • Go类型也可以有类型参数列表,比如 type M(type T) []T
  • 每一个类型参数可以有一个额外的类型约束: func F(type T Constraint)(p T) { ... }
  • 类型约束是接口类型
  • 用作类型约束的接口有一个预先声明的类型列表;如果类型或者它的底层类型是这个列表中的类型之一,那么它们才算实现了这个接口 (对接口的定义进行了扩展)
  • 使用泛型函数或者泛型类型的时候,需要传入类型参数
  • 在通常情况下,类型推断允许省略类型参数(type argument)
  • 如果类型参数有类型约束,那么它的类型参数(type argument)必须要实现这个接口
  • 泛型函数只能使用类型约束所定义的那些操作

这就是当前Go泛型的主要内容。如果你已经接触过其他语言的泛型,就比较好理解Go的泛型的概念,而且会感觉它很简单,因为很多复杂的概念都省略了(下面内容比较难以理解):

  • No specialization.不像Rust可以针对特定的类型参数进行特化
  • No metaprogramming. 没有元数据的支持(反射package不会因此改变)
  • No higher level abstraction. 不调用或者初始化函数就无从谈起带类型参数的函数
  • No general type description. 只通过类型约束(接口)来约束类型参数的行为
  • 没有协变和逆变. 简化了泛型的概念。
  • 没有操作符方法.
  • 不支持currying. 使用泛型函数和类型时,必须全部指定所有的类型参数。
  • 不支持变长类型参数.
  • 不支持适配器
  • 对非类型值(如常量)不支持参数化.

类型参数的定义使用 type
,而且使用小括号而不是尖括号的方式:

// 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
}

我个人强烈反对这个小括号的方式,因为它很容易和函数的定义弄混,本来你在定义一个数据类型的,第一眼瞄过去很容易看成是一个函数定义。对于函数来说,两个连续的小括号也很容易让人视觉疲劳。为什么不采用Java/C++泛型语法结构呢,比如 F
,理由比较勉强,是为了照顾Go的parser,避免go parser在解析尖括号的时候避免和操作符混淆,但是人类发明工具不就是为了简化人们的操作,让工具去实现枯燥的逻辑么?同样不采用 F[T]
的形式也是为了避免和数组混淆。
下面是一个使用类型约束的例子:

type Stringer interface {
    String() string
}

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

多类型参数的例子:

func Print2(type T1, T2)(s1 []T1, s2 []T2) { ... }

泛型类型而不是函数,不要和函数定义混淆了:

type Vector(type T) []T

不支持类型的方法中使用类型参数,这一点和其它语言不相同,但是它可以使用类型定义中的类型参数。
接口的定义扩展了,支持显式地定义实现的类型:

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

SignedInteger
约束指定了类型参数必须是列表中的几种类型,或者底层结果是这几种类型的类型。当使用 SignedInteger
约束类型参数的时候,我们可以使用这些类型共同的操作符,比如 >
<
等。

comparable
是一个预定义的约束,可以使用 ==
!=
比较符。

类型推断可以简化泛型的调用,比如 Print(int)([]int{1, 2, 3})
就可以省略类型参数 Print([]int{1, 2, 3})

然后我们欣赏一个例子做结尾。虽然Go的泛型方案做了很多简化,但是依然会给Go编程开发带来很大的变化,目前可以这依然还是一个草稿,等到提案文档发布的时候,Go泛型的特性才算基本定型,对于绝大部分的人来说,目前只需保持关注即可。

// 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
}