Go 泛型 第一篇

Go的泛型已经讨论了多年了,具体热烈的讨论可以参考

https://github.com/golang/go/issues/43651这个issue。

自从草案设计以来,在漫长的等待后,该提案终于在2020年3月发布了。
泛型应该在Go1.18,也就是2022年2月可以用起来。

由于提案内容非常多,把它全部读完很费时间。我将尝试以更简洁的方式用示例介绍关键特性。

如何开始Go泛型

Go团队提供了两种方法,以便在官方发布之前尝试新功能。

  • https://gotipplay.golang.org/ 一个在线的的运行Go代码平台

  • go2go tools可以本地安装,具体安装方法

#下载源码
git clone https://go.googlesource.com/g

# checkout到go2go分支
cd go && git checkout dev.go2go

# 编译
cd src && ./make.bash

# 安装build tools
GO111MODULE=on go get golang.org/x/tools/gopls@dev.go2go golang.org/x/tools@dev.go2go

然后,你就可以通过创建.go2GO文件来体验新功能!

理解Go泛型

主要功能包括以下两个部分:

  • 类型参数。这里的Type可以是函数的参数类型,也可以是结构中字段的类型。

  • 约束。一般情况, 约束使用相同的行为(func)组合多个类型,使这些类型的任何实例对象都能运行相应的泛型方法。如果你觉得约束部分的解释有点抽象,下面这个例子可以帮助你更好地理解。

函数类型参数

看看官方给出的最简单的例子

import(
  "fmt"
)
func Print(T any)(s []T) {
  for _, v := range s {
    fmt.Printf("%v ", v)
  }
}
func main() {
  PrintSlice(int)([]int{1, 2, 3, 4, 5})
}

T any表示函数参数的类型。因此,该函数可以打印元素类型为T的切片的所有元素。
因为可以直接打印所有Go类型,main函数中声明的类型(int)可以省略,Go编译器可以进行正确的类型推断。
所以这种写法也合理。

Print([]string{"Hello, ", "World\n"})

再写个更复杂的例子。

func Map[K, V any](s []K, transform func(K) V) []V {
  rs := make([]V, len(s))
  for i, v := range s {
  	rs[i] = transform(v)
  }
  return rs
}

func main() {
  arr := []string{"a", "b"}
  dArr := Map(arr, func(s string) string { return s + s})
  fmt.Println(dArr);
}
// output: [aa bb]

结构体中类型参数

比如可以像下面这样写:

type Vector[T any] []T

type LinkedList[T any] struct {
  next *LinkedList[T]
  val T
}

type Pair[T1, T2 any] struct {
  v1 T1
  v2 T2
}

type Tuple[T1, T2, T3 any] struct {
  v1 T1
  v2 T2
  v3 T3
}

然后,就可以从这些结构中抽象出通用的方法。比如找到最小的元素

func Min[T any](s []T) T {
  r := s[0]
  for _, v := range s[1:] {
  	if v < r {
    	r = v
  	}
  }
  return r
}

但是 上面的代码没办法编译通过,报错如下:

./prog.go:27:7: invalid operation: cannot compare v < r (operator < not defined on T)

报错的意思是: 在这里不能比较v和r,因为vrT类型,而T类型没有比较操作符

有很多解决方案。比如,可以抽象<的比较操作,定义一个类似于Comparable(在Java中)的比较函数,
然后在调用时传入compare函数。实现如下。

func Min[T any](s []T, compare func(T, T) int) T {
  r := s[0]
  for _, v := range s[1:] {
    if compare(r, v) == 1 {
      r = v
    }
  }
  return r
}

func main() {
  arr := []string{"a", "b"}
  dArr := Map(arr, func(s string) string { return s + s })
  fmt.Println(dArr)

  v := make(Vector[int], 3)
  v[0] = 1
  v[1] = 2
  v[2] = -1
  fmt.Println(Smallest(v, func(i1 int, i2 int) int {
    if i1 > i2 {
      return 1
    } else if i1 == i2 {
      return 0
    }
    return -1
  }))
}

约束(Constraint)

上面的Min可以实现功能,但是有点多余,因为你需要为每种类型(int、uint、int32和string)定义相应的比较函数时,才能使用Min方法。

更好的做法是定义约束Constraint。我们知道所有整型都支持<>操作符。因此,只要定义一个包含所有类型的集合,就不再需要分别为每种类型定义比较函数。

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

然后需要修改下Vector的实现

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

type Vector[T SignedInteger] []T

func main() {
   arr := []string{"a", "b"}
   dArr := Map(arr, func(s string) string { return s + s })
   fmt.Println(dArr)
   v := make(Vector[int], 3)
   v[0] = 1
   v[1] = 2
   v[2] = -1
   fmt.Println(Min(v))
}

func Min[T SignedInteger](s []T) T {
    r := s[0]
    for _, v := range s[1:] {
    	if r > v {
    		r = v
        }
    }
    return r
}

泛型受益

使用泛型的代码有很多方便之处。

  • 编译时执行强类型检查。这非常有用,因为如果泛型代码违反了类型安全,编译器将报错。编译过程中的错误比运行时中的错误更容易修复。

看下面的例子, 这里并不是所有类型都实现==操作符,并且会报告错误。

func test[T any](s []T) T {
  for _, v := range s {
    if v == r {
    }
  }
  return r
}
  • 消除类型转换,使代码更丝滑,减少各种类型转换,并提高可读性。

  • 减少重复的代码。可以在GO中实现类似Java集合的API,同时确保类型安全。

泛型内部

泛型本质上是一种语言糖。Go团队修改了编译器,在词法分析和语义分析阶段解析了[]操作符,然后确定是否符合泛型语法。
你可以使用go2go工具查看相关的编译代码。

go tool go2go translate xx.go2

为了进一步分析,还需要等待即将发布的官方团队的发布和更多工具。

提供一个有用的例子

和我一样,大多数开发人员肯定已经在考虑如何将泛型用到应用程序中,
同时等待发布。我的一个实践是使用它来实现类型安全映射和过滤

package main

import (
	"fmt"
)

func mapFunc[K any, V any](a []K, f func(K) V) []V {
	n := make([]V, len(a), cap(a))
	for i, e := range a {
		n[i] = f(e)
	}
	return n
}

func filterFunc[K any](a []K, f func(K) bool) []K {
	var n []K
	for _, e := range a {
		if f(e) {
			n = append(n, e)
		}
	}
	return n
}

func main() {
	m := filterFunc(
		mapFunc([]int{1, 2, 3, 4, 5, 6},
			func(v int) int {
				return v * v
			},
		),
		func(v int) bool {
			return v < 20
		})
	fmt.Println(m)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值