Search code examples
gogenericshigher-order-functions

How can I implement a generic filter function?


Suppose I'm implementing this function for filtering a Slice in Golang:

 func Filter(filter func(n int) bool) func(list []int) []int {
   return func(list []int) []int {
     r := make([]int, 0)
     for _, n := range list {
         if filter(n) {
             r = append(r, n)
         }
   }

     return r
 } 
}

To be used like this:

list := []int{1, 4, 3, 2, 7, 4, 9, 7}
r := Filter(func(n int) bool { return n > 3 })(list)

fmt.Println(r)

This works ok, but I have the following questions:

  1. Am I supposed to use the full func syntax instead of a lambda style expression?
  2. If I want my Filter to filter slices of ANY type, what return type should I use?

Thanks!


Solution

    1. As far as I know, no proposal for a more concise anonymous-function notation ("lambda") has been accepted yet.

    2. The addition of type parameters (a.k.a. generics) to the language is planned for early 2022 with the release of Go 1.18. Then, you'll be able to write the program below (playground).

      If you can wait until then, do. At any rate, using the reflect package and peppering one's code with the empty interface{} and type assertions are generally discouraged. One viable alternative until Go 1.18 would be to use go generate to generate the different specializations (for int, string, etc.) you need.

      package main
      
      import "fmt"
      
      func Filter[T any](filter func(n T) bool) func(T []T) []T {
          return func(list []T) []T {
              r := make([]T, 0, len(list))
              for _, n := range list {
                  if filter(n) {
                      r = append(r, n)
                  }
              }
              return r
          }
      }
      
      func main() {
          list := []int{1, 4, 3, 2, 7, 4, 9, 7}
          r := Filter(func(n int) bool { return n > 3 })(list)
          fmt.Println(r)
      
          list2 := []string{"foo", "bar", "baz", "qux", "quux"}
          r2 := Filter(func(s string) bool { return len(s) <= 3 })(list2)
          fmt.Println(r2)
      }