Implement Higher Order Functions in Go Lang – Map, Reduce and Filter

Higher-order functions are a fundamental concept in functional programming, and Go is no exception. In some time ago we explored how to implement some of the higher order functions in Python,you can check out the video here .

In this tutorial we will learn how to implement them in Go.

In Go, higher-order functions are functions that take other functions as arguments or return functions as output. In this article, we’ll explore how to implement three common higher-order functions in Go: mapreduce, and filter. We’ll also touch on the zip function, which is not strictly a higher-order function but is often used in conjunction with higher-order functions.

Map Function

The map function takes a function and a slice of inputs (or sequence), and applies the function to each input in the slice, returning a new slice of outputs. Here’s an example implementation of map in Go:

First we will implement a map function that accepts only integer and return a slice of integer and then we will make it more robust to support other data types via Go Generics.

func myMap(sequence []int, fn func(int) int) []int {
	results := []int{}
	for _, i := range sequence {
		results = append(results, fn(i))
	}
	return results
}

How do we use this our custom map function

package main

import "fmt"

// our function
func multiply6(a float64) float64 {
	return a * 6
}

func main(){
results := myMap([]int{2,4,6,7},multiply6)
fmt.Println(results)
}

How do we support other data types? To do so we will use Go Generics (T) and create a generic function

func myMap[T, U any](fn func(T) U, inputs []T) []U {
    outputs := make([]U, len(inputs))
    for i, input := range inputs {
        outputs[i] = fn(input)
    }
    return outputs
}

This implementation takes a function fn that takes a single input of type T and returns a single output of type U. It also takes a slice of inputs inputs of type []T. The function returns a new slice of outputs outputs of type []U.

The order of the argument in this function was changed to make it more like how map is used in python.

To use map, we can define a function that takes a string and returns its uppercase version, and then apply it to a slice of strings:

func toUpper(s string) string {
    return strings.ToUpper(s)
}

inputs := []string{"hello", "world", "go"}
outputs := myMap(toUpper, inputs)
fmt.Println(outputs) // Output: ["HELLO", "WORLD", "GO"]
// or with support for int
results := myMap(multiply6,[]int{2,4,6,7})

Reduce

The reduce function takes a function, an initial value, and a slice of inputs, and applies the function to each input in the slice, starting with the initial value. The function returns the final result of the reduction. Here’s an example implementation of reduce in Go:

func myReduce[T, V any](fn func(V, T) V,sequence []T,  initValue V) V {
	acc := initValue
	for _, i := range sequence {
		fmt.Println("Initial", acc)
		acc = fn(acc, i)
		fmt.Println("Current", acc)
	}
	return acc
}

This implementation takes a function fn that takes two inputs of type T and returns a single output of type T. It also takes an initial value of type T and a slice of inputs inputs of type []T. The function returns the final result of the reduction, which has type T.

How do we apply the custom reduce function then?

func find_max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

max_values := myreduce(find_max,[]int{1, 3, 2, 10, 5, 6, 7},  0)
fmt.Println(max_values)

We can also use the idea behind reduce to implement a sum of a list or sequence.

func add(a, b int) int {
    return a + b
}

inputs := []int{1, 2, 3, 4, 5}
result := myReduce(add, inputs,0)
fmt.Println(result) // Output: 15

Filter

The filter function takes a function and a slice of inputs, and returns a new slice of inputs that satisfy the condition defined by the function. Here’s an example implementation of filter in Go:

// filter: sequence if condition is meet it returns those that meet the condition
func myFilter(fn func(int) bool,sequence []int) []int {
	results := []int{}
	for _, v := range sequence {
		if fn(v) {
			results = append(results, v)
		}
	}
	return results
}

// support for any data type via generics
func Filter[T any](fn func(T) bool, inputs []T) []T {
    var outputs []T
    for _, input := range inputs {
        if fn(input) {
            outputs = append(outputs, input)
        }
    }
    return outputs
}

This implementation takes a function fn that takes a single input of type T and returns a boolean value indicating whether the input satisfies the condition. It also takes a slice of inputs inputs of type []T. The function returns a new slice of inputs outputs that satisfy the condition, which has type []T.

To use filter, we can define a function that takes a number and returns true if the condition is meet, and then apply it to a slice of numbers:

func greaterThanZero(n int) bool {
    return n > 0
}

func isOdd(x int) bool {
	return x%2 == 1
}


inputs := []int{-1, 0, 1, 2, 3}
outputs := Filter(greaterThanZero, inputs)
fmt.Println(outputs) // Output: [1, 2, 3]


odd_values := myfilter(isOdd,[]int{1, 3, 2, 10, 5, 6, 7})
fmt.Println(odd_values)

Zip

The zip function takes two or more slices of the same length and returns a slice of tuples, where each tuple contains a value from each slice. Here’s an example implementation of zip in Go:

func Zip[T any](slices ...[]T) [][]T {
    var outputs [][]T
    for i := range slices[0] {
        output := make([]T, len(slices))
        for j, slice := range slices {
            output[j] = slice[i]
        }
        outputs = append(outputs, output)
    }
    return outputs
}

This implementation takes a variable number of slices slices of type []T, where T is a type parameter. The function returns a slice of tuples outputs of type [][]T.To use zip, we can define two slices of strings and apply them to zip:

input1 := []string{"hello", "world", "go"}
input2 := []string{"there", "you", "are"}
outputs := Zip(input1, input2)
fmt.Println(outputs) // Output: [["hello" "there"], ["world" "you"], ["go" "are"]]

In this example, input1 and input2 are two slices of strings, and Zip returns a slice of tuples, where each tuple contains a string from input1 and a string from input2.

You can check out the video tutorial for these implementation below

In this post we’ve explored how to implement higher-order functions in Go, including mapreducefilter, and zip. These functions are powerful tools for manipulating and transforming data , and can help you write more concise and expressive code. 

Thank You For Your Attention

Jesus Saves

By Jesse E.Agbe(JCharis)

Leave a Comment

Your email address will not be published. Required fields are marked *