SoFunction
Updated on 2025-03-05

Learn to improve Go language coding efficiency skills and refuse to work overtime!

introduction

In Go, slice and map are the basic types we use frequently, through which we can easily process data. However, you may notice that in order to handle both of these data, we have to write many practical functions.

For example, find the location of an element from a `slice` slice. This kind of search is divided into previous search and later search. Or, get all keys of `map`? Or all values?

For example, in JavaScript, the array's `map`, `reduce`, `filter` and other functions are very useful, but unfortunately, the Go standard library does not provide such functions.

There are many other examples, and they are practical functions that we often need in programming. However, our Go SDK does not provide these.

So what should we do?

One way is to write a toolkit yourself for use with the project, but the maintenance of this toolkit may become a problem and requires manpower.

go-funk

The second way is to use open source libraries, fully tested, verified, and updated frequently to ensure availability.

Because we are facing common problems, some people have made open source libraries for everyone to use.

Such a tool library takes Go 1.18 as the dividing line, and the more famous one before Go 1.18 was `go-funk`.

It has an interesting name Repo

The address is /thoas/go-funk

It is officially introduced as a modern Go utility library:

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

It provides many practical functions, such as `Contains`, `Difference`, `IndexOf`, `LastIndexOf`, etc. For more details, please refer to its documentation.

However, it has a fatal problem, which is the use of reflection. This is also unavoidable, because before Go generics are not supported, functions that satisfy different types can only be written through reflection.

For example, if you do not use reflection, if you want to support more types, you have to define many functions with similar names, as shown below:

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

Is the above function OK? Of course, it's OK, but I can write a lot of duplicate code, and it looks weird.

Before the generic support of Go languages, to solve this problem, reflection can only be done.

Before the generic support of Go languages, general tool functions that handle slices and maps usually require reflection.

While this approach can help us write more general code, it also brings some performance overhead.

For example, consider a general lookup function for finding specific elements in a slice:

go
Copy code
import (
    "reflect"
)
func indexOf(slice interface{}, target interface{}) int {
    sliceValue := (slice)
    for i := 0; i < (); i++ {
        element := (i).Interface()
        if element == target {
            return i
        }
    }
    return -1
}

The above function uses reflection, which can accept slices of any type and find the target element there. However, this flexibility comes at the expense of performance, as the overhead of reflection is relatively high.

Before the introduction of generics in Go 1.18, we had to write similar general functions on different types of slices and maps, which could lead to a lot of duplicate code. In practical applications, we need to weigh the universality and performance overhead of the code.

It is worth noting that generic support for Go has already provided a more elegant and efficient solution that enables universality without using reflection.

Writing more intuitive and type-safe general functions using generics

In Go 1.18 and later, you can use generics to write more intuitive and type-safe general functions.

// 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 := (in)
  elemValue := (elem)
  inType := ()
  if () ==  {
    return ((), ())
  }
  if () ==  {
    equalTo := equal(elem)
    for i := 0; i < (); i++ {
      if equalTo({}, (i)) {
        return i
      }
    }
  }
  return -1
}

Generic code is complex and inefficient. . .

So after Go 1.18, generics have been supported. Can it be rewritten using generics?

The answer is: Of course, and someone has done so. This library is/samber/lo

IndexOf function implementation

It is based on Go generic implementation, does not use reflection, is efficient and has concise code. For example, the IndexOf function just now is implemented in this library:

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

Just need T to be constrained to comparable, you can use the == symbol for comparison. The overall code is very simple and has no reflection.

IndexOf is just one of dozens of functions. These functions basically cover slice, map, string and other aspects, involving search, comparison of size, generation, map, reduce, filtering, filling, inversion, grouping, etc. Use methods and examples can be used.go doc documentation

The above is to learn to improve the efficiency of Go language coding skills and refuse to work overtime! For more information on the details, please pay attention to my other related articles about improving Go language coding efficiency!