Skip to content

Latest commit

 

History

History
233 lines (167 loc) · 11.7 KB

Learning Go 09 泛型.md

File metadata and controls

233 lines (167 loc) · 11.7 KB

Table of Contents

为什么用泛型

为什么人们想要泛型

Generics Reduce Repetitive Code and Increase Type Safety.

If we wanted a binary tree for strings or float64s and we wanted type safety, we would have to write a custom tree for each type. That’s verbose and error-prone. It would be nice to write a single data structure that could handle any type that can be compared with a <, but Go doesn’t let you do that today.

While data structures without generics are inconvenient, the real limitation is in writing functions. Rather than write multiple functions to handle different numeric types, Go implements functions like math.Max, math.Min, and math.Mod using float64 parameters, which have a range big enough to represent nearly every other numeric type exactly. (The exceptions are an int, int64, or uint with a value greater than 2^53 – 1 or less than –2^53 – 1)

没有泛型的诸多限制

You also cannot create a new instance of a variable that’s specified by interface, nor can you specify that two parameters that are of the same interface type are also of the same concrete type. Go also doesn’t provide a way to process a slice of any type; you cannot assign a []string or []int to a variable of []interface{}. This means functions that operate on slices have to be repeated for each type of slice, unless you resort to reflection and give up some performance along with compile-time type safety (this is how sort.Slice works).

The result is that many common algorithms, such as map, reduce, and filter, end up being reimplemented for different types. While simple algorithms are easy enough to copy, many (if not most) software engineers find it grating to duplicate code simply because the compiler isn’t smart enough to do it automatically.

Idiomatic Go and Generics

Adding generics clearly changes some of the advice for how to use Go idiomatically. The use of float64 to represent any numeric type will end. We will no longer use interface{} to represent any possible value in a data structure or function parameter. You can handle different slice types with a single function. But don’t feel the need to switch all of your code over to using type parameters immediately. Your old code will still work as new design patterns are invented and refined.

什么时候适合用泛型

  • Functions that work on slices, maps, and channels of any element type. ( 例如从 map 生成 key 列表 )
  • General purpose data structures. For exmaple, a linked list or binary tree.
  • When a method looks the same for all types.

➤ 如果只是为了调用特定方法,应该用接口

func BadRead[T io.Reader](r T) {
    r.Read(nil) // 如果只会用到 Read 方法那么别用泛型啊, 接口 io.Reader 比泛型更简单易读
    var s []T   // 像这样用得上类型参数时, 才使用泛型
    s = append(s, r)
}

使用泛型的一个例子

// "T" is type parameter and "any" is type constraint.
type Stack[T any] struct {
    vals []T
}

// We refer to the type in the receiver section with Stack[T] instead of Stack.
func (s *Stack[T]) Push(val T) {
    s.vals = append(s.vals, val)
}

func (s *Stack[T]) Pop() (T, bool) {
    // Finally, generics make zero value handling a little interesting.
    // In Pop, we can’t just return nil, because that’s not a valid value for a value type, like int.
    // To get a zero value for a generic, we simply declare a variable with var and return it.
    if len(s.vals) == 0 {
        var zero T
        return zero, false
    }
    top := s.vals[len(s.vals)-1]
    s.vals = s.vals[:len(s.vals)-1]
    return top, true
}

func TestStack(t *testing.T) {
    var s Stack[int]
    s.Push(10)       // 类型安全,  禁止 push 字符串
    s.Push(20)       // 使用方便,  返回值无需 type assertion
    s.Push(30)       // 减少重复,  不用为 string、float64 类型重写一遍 Stack
    v, ok := s.Pop() // 用第二个返回值 ok 表示「 值不存在 」是 Go 的常用套路
    fmt.Println(v, ok)
}

使用泛型

泛型由三个部分构成:

  1. Type parameters for functions and types
  2. Type sets defined by interfaces
  3. Type inference

关于 Type Parameter

  1. 和函数参数很像,但用方括号: [P, Q constraint, R constraint]

  2. 类型参数的意义是让 function / type 可以使用 type 作为输入参数,例如:

    func Min[T constraints.Ordered](a, b T) T {
        if a < b {
            return a
        }
        return b
    }
    
    func TestInstantiation(t *testing.T) {
        minInt := Min[int]               // 泛型函数 Min 使用 int 类型作为类型参数 T 的输入
        minFloat := Min[float64]         // 像这样为泛型函数传入类型, 叫做实例化 ( instantiation )
        fmt.Println()                    //
        fmt.Println(minInt(1, 2))        // 编译器会用具体类型替换类型参数, 例如用 int 替换 T
        fmt.Println(minFloat(1.25, 2.5)) // 并检查 int 是否满足 Ordered 约束, 若不满足则实例化失败
    }

关于 Type Set

  1. Type Set 就是一组类型,它表示 T 的取值范围,例如 constraints.Ordered 包含 int/float64/... 等类型

  2. Type Set 也叫 Type Constraint,例如 [T constraints.Ordered] 限制了 T 必须是可比较大小的类型

  3. 使用 interface 定义 type set:

    // 这个 type set 包含 Integer、Float、和所有底层类型为 string 的类型 ( 写作 ~string )
    type Ordered interface {
        Integer | Float | ~string
    }
  4. 只能调用 type set 中共有的东西:

    type IntOrString interface {
        Integer | string
    }
    
    func double[T IntOrString](x T) T {
        return x + x // 合法, 因为 + 操作对 Integer 和 string 都适用
        return x * 2 // 非法, 因为 T 可能是 string 类型, 此时不支持 * 2 操作
    }
  5. Interfaces used as constraints may be given names (such as Ordered), or they may be literal interfaces inlined in a type parameter list. For example: [S interface{~[]E}, E interface{}] ( S 是 ~[]E 类型,E 是任意类型 )

  6. Because this is a common case, the enclosing interface{} may be omitted for interfaces in constraint position, and we can simply write: [S ~[]E, E interface{}]. Because the empty interface is common in type parameter lists, Go 1.18 introduces a new identifier any. Finally, we arrive at this idiomatic code: [S ~[]E, E any]

关于 Type Inference

In some situations, type inference isn’t possible (for example, when a type parameter is only used as a return value). When that happens, all of the type arguments must be specified:

// T2 仅仅作为返回值, 无法做类型推断
func Convert[T1, T2 Integer](in T1) T2 {
    return T2(in)
}

func TestInference(t *testing.T) {
    var a int = 10
    b := Convert[int, int64](a) // 需要显式指定类型
    fmt.Println(b)
}

几个常用约束

func process[T any](x T) {
    // T 的约束是 any 类型, 所以不能对 x 变量调用任何方法, 但能把 T 放到容器里面
    var s []T
    s = append(s, x)
}

(1) 内置类型约束:

  • anyinterface{} 的别名,可以是任意类型
  • comparable 表示可判断相等性的类型,可以用 ==!= 运算符

(2) 下载 golang.org/x/exp/constraints 包:

  • constraints.Ordered 表示可比较大小的类型,可以用 < <= >= > 运算符
  • constraints.Integer 表示所有整数类型 ( 以及底层类型为整数的类型 ),比如 int uint int32 uint32 ...
  • constraints.Float 表示所有浮点数类型 ( 以及底层类型为浮点数的类型 ),比如 float32 float64 ...

例子 - 泛型接口

image-20220731214239340

例子 - 泛型函数

Earlier we mentioned that not having generics made it difficult to write map, reduce, and filter implementations that work for all types. Generics make it easy.

image-20220801150619604

Type Set

操作符取交集

While we can use our Tree with built-in types when there’s an associated type that meets Orderable, it might be nice to use a Tree with built-in types that didn’t require the wrappers. To do that, we need a way to specify that we can use the < operator. Go generics do that with a type set, which is simply a list of types specified within an interface:

image-20220801144739900

When a type constraint is specified with an interface containing a type list, any listed type can match. However, the allowed operators are the ones that apply for all of the listed types. In this case, those are the operators ==, !=, >, <, >=, <=, and +.

Type lists also specify which constants can be assigned to variables of the generic type. There are no constants that can be assigned to all of the listed types in BuiltInOrdered, so you cannot assign a constant to a variable of that generic type.

声明过的方法才能用

Specifying a user-defined type does not give you access to the methods on the type. Any methods on user-defined types that you want to access must be defined in the interface, but all types specified in the type list must implement those methods, or they will not match the interface.

在下面的例子中, int 类型虽然能匹配 ~int, 但没有实现 String 方法, 所以 DoubleString 函数不能传 int

image-20220801163402061

奇怪问题

请解释图中的问题

➤ 为什么 Scale(p, 10) 返回的是 []int 类型?

image-20220801201717300

调用 Scale 时, E 被实例化成 int, 所以返回值是 []int, 而不是 Point.
解决办法如下, S 可以是任意底层类型为 []E 的类型,调用 Scale2 时 S 被实例化成 Point, 所以返回值也是 Point 类型:

image-20220801202312424