在 Go 语言中,slice、map都是我们常用的基础类型,通过它们,我们可以很容易的使用数据。但是你有没有发现,为了对这两种数据进行处理,你不得不编写很多工具函数?

比如,从slice切片中查找一个元素的位置?这种查找又分为从前查找、从后查找。

又比如,获取map的所有keys?或者所有的value?

再比如,JS语言数组的map、reduce、filter函数,这在编程中非常好用,但是遗憾的是Go标准库没有提供。

这些例子,还有很多,而且都是我们编程中常用的工具函数,但是我们的 Go SDK 却没有。

但是我们又需要怎么办?一种办法,是我们自己编写一个工具包,用于给项目使用,但是这个工具包的维护,又是一个问题,需要人力。

第二种方式,就是用开源的库,经过充分的测试、验证,并且经常更新,所以可以保障。

因为我们遇到的是常见的问题,所以有人做了开源库,以供大家使用。这种工具库以 Go 1.18为分界线,Go 1.18之前有一款比较出名的是 go-funk。

很有意思的名字,Repo 地址是 https://github.com/thoas/go-funk。官方介绍就是一个Go语言工具库:

A modern Go utility library which provides helpers (map, find, contains, filter, …)

它提供了很多好用的函数:Contains、Difference、IndexOf、LastIndexOf等等,具体更多的可以参考它的网站。

但是它有一个致命的问题,就是用到了反射。这也是没办法的事情,因为在Go 泛型没有支持之前,只能通过反射才能写出满足不同类型的函数。

举个IndexOf的例子,假如不用反射,要想支持更多的类型,就得定义很多相似名称的函数,如下所示。

func IndexOfBool(a []bool, x bool) int {


}

func IndexOfInt(a []int, x int) int {


}

func IndexOfInt32(a []int32, x int32) int {


}

func IndexOfInt64(a []int64, x int64) int {


}

func IndexOfUInt(a []uint, x uint) int {


}

func IndexOfUInt32(a []uint32, x uint32) int {


}

func IndexOfUInt64(a []uint64, x uint64) int {


}
func IndexOfFloat64(a []float64, x float64) int {

}
func IndexOfString(a []string, x string) int {

}

以上函数行不行?当然行,但是会写很多重复的代码,并且看着也怪怪的。

在Go语言的泛型支持之前,要解决这个问题,只能通过反射。

// IndexOf gets the index at which the first occurrence
// of value is found in array or return -1
// if the value cannot be found
func IndexOf(in interface{}, elem interface{}) int {
  inValue := reflect.ValueOf(in)

  elemValue := reflect.ValueOf(elem)

  inType := inValue.Type()

  if inType.Kind() == reflect.String {
    return strings.Index(inValue.String(), elemValue.String())
  }

  if inType.Kind() == reflect.Slice {
    equalTo := equal(elem)
    for i := 0; i < inValue.Len(); i++ {
      if equalTo(reflect.Value{}, inValue.Index(i)) {
        return i
      }
    }
  }

  return -1
}

泛型代码复杂,并且效率低。。。

那么Go 1.18之后已经支持了泛型,能不能用泛型来重写呢?

答案是:当然可以,并且已经有人这么做了。这个库就是 https://github.com/samber/lo ,也是我们今天要介绍的主角,开源3个月,6000多star!!!

它是基于Go泛型实现,没有用到反射,效率高,代码简洁。比如刚刚的IndexOf函数,在该库中是这么实现的:

// IndexOf returns the index at which the first occurrence of
// a value is found in an array or return -1
// if the value cannot be found.
func IndexOf[T comparable](collection []T, element T) int {
  for i, item := range collection {
    if item == element {
      return i
    }
  }

  return -1
}

只需要 T 被约束为comparable 的,就可以使用==符号进行比较了,整体代码非常简单,并且没有反射。

IndexOf只是lo几十个函数中的一个,这些函数基本上覆盖了slice、map、string等方方面面,涉及查找、比较大小、生成、map、reduce、过滤、填充、反转、分组等等,使用方法和示例,可以参考go doc文档。