TS and Go 之 泛型

350 阅读3分钟

我们知道,Go 最开始是不支持泛型的,最近版本(1.18)才开始支持。在支持泛型之前,一些夸类型的操作不得不通过反射来实现。那如何在 Go 中来使用泛型呢,我们可以跟我们熟悉的 TS 来做一个比较。

泛型的基础概念

基本的泛型函数

如果没有泛型,我们就需要分别定义多个函数,或者用 any 作为类型。通过泛型,我们可以对不同类型的参数做类似的处理。比如:

func minValue[T float32 | float64 | int16 | int32 | int64 | int](a T, b T) T {
        if a < b {
                return a
        }

        return b
}

minValue[int](1, 2)

TypeScript 中的实现类似:

function min<T extends number | string>(a: T, b: T) {
    if (a < b) {
        return a;
    }
    return b;
}
  • TS 中用 <> 来表述泛型
  • TS 中需要使用 extends 关键字来描述约束
  • TS / Go 的泛型都写在函数名后面

泛型的类型推断

这里,我们在函数调用的时候制定了类型约束,实际上我们已经可以通过函数参数的上下文得知,我们的类型约束为 int, 这里可以简写成:

minValue(1, 2)

TS 中类似,可以省略泛型的约束制定。

定义类型约束

在泛型的类型约束中,我们会写的比较长,是否可以进行约束的定义呢。我们可以使用 interface 关键字来进行类型约束定义:

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

func minValue[T Number](a T, b T) T {
    if a < b {
        return a
    }

    return b
}

TS 中通过联合类型实现:

type NumberAndString = string | number;

// TS 中的联合类型不仅支持普通类型,也支持值类型:
type MyNumber = 1 | 2;

~ 关键字的使用

有时候,我们会使用自定义类型, 比如:

type MyFloat float32

var a1 MyFloat = 0.1
var a2 MyFloat = 0.2

minValue(a1, a2)

这里会出现类型错误:MyFloat does not satisfy Number (possibly missing ~ for float32 in Number) 其实很容易,如果需要支持自定义的类型,只需要在类型约束前加上:~,比如:

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

TS 中不做区分:

类型约束和接口

我们知道 interface 既可以用来做类型约束,也可以用来做接口定义。某种程度上说,他们是一致的,比如:

  • 我们通过约束 C1 定义泛型函数:
type C1 interface {
    ~int | ~int32
    M1()
}

func foo[P C1](t P) {
}
  • 同时我们定义 T 实现接口,但是不实现类型约束:
type T struct{}
func (T) M1() {}

var t T
foo(t) // 编译器报错:T does not satisfy C1 (T missing in ~int | ~int32)
  • 如果我们实现了类型约束但是没有实现接口:
type T int
var t T
foo(t) // T does not satisfy C1 (missing method M1)
  • 只有同时满足了类型约束和接口实现才能通过编译:
type T int
func (T) M1() {}

var t T
foo(t) // 编译器报错:T does not satisfy C1 (T missing in ~int | ~int32)

类型的泛型

定义类型的时候,我们也可以制定泛型。比如:

type MyList[T int | string] []T

这里 TS 和 Go 是类似的:

type MyList<T extends number | string> = T[]

当然,TS 的类型系统要强大的多,类型本身是可以赋予逻辑的,比如:

// 可以通过 extends 对泛型进行判断。
type MyList<T extends number | string> = T extends string ? true : false
const data: MyList<string>

// 通过 keyof 对类型进行计算:
type KeyList<T> = keyof T;
const data: KeyList<{a: string, b: string}>

Struct (class) 的泛型

  • 我们可以对 struct 的定义增加泛型:
type MyList[T ~int | ~int32] struct {
    data []T
}
func (c MyList[T]) getFirst() *T {
    if c.data == nil {
        return nil
    }

    return &c.data[0]
}
  • Go 不支持 method 中定义约束。(只能使用 struct 上的约束)TS 可以同时在 class, method 上定义不同的约束:
class MyList<T extends string | number> {
  data: T[];

  getFirst<N extends string | number>(name: N): T {
    console.log(name);
    return this.data[0];
  }
}