YURKOL Ltd - Custom Software and Cloud Architectural Solutions for Modern Businesses

Generic Map Functions in Go.

Javascript arrays have foreEach() method (which I honestly miss in Go). It's an iterative method, that calls a provided callback function once for each element in an array in ascending-index order.
The Map Functions pattern in Go pretty closely resembles this Javascript method, - I suspect it is called this way because the callback gets mapped to each element.
With introduction of generics, implementation of this pattern has become as easy as it's never been.

File go-sketches.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package go_sketches

// implement 'foreach' function from JS
// using generics

type Array[T any] []T

// ForEach resembles Javascript arrays' "forEach" method
// it's also a good exercise in pointers
func (a Array[T]) ForEach(f func(item *T)) {
	for i, _ := range a {
		// process everything in place, so the space complexity is O(n),
		// not O(2n) ;-)
		// remember: in the loop any collection item is passed by value!
		// this is why we're getting each element by its index, and then
		// using pointer to it
		f(&(a[i]))
	}
}

[T any] in the type definition is a type constraint, and our newly defined type Array can contain elements of any type (still, all the elements have to be of the same type). Sometimes this is mistakenly confused with interface{} type, which is incorrect. Generic type constraints have nothing to do with empty interface, and it's the compiler's job to infer the concrete type of a generic element.
Similar syntax is used in our ForEach method definition, where *T indicates that the method expects a pointer to the data of any type as its argument.


And now let's make sure that everything works as expected. For unit testing I'm using great Testify package.
File sketches_test.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package go_sketches

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

// nice exercise in pointers as well
func SquareInt(i *int) {
	*i = (*i) * (*i)
}

func TestArray_ForEach(t *testing.T) {
	a := Array[int]{1, 2, 3, 4, 5}
	a.ForEach(SquareInt)
	exp := []int{1, 4, 9, 16, 25}
	assert.EqualValues(t, exp, a)
}

Run the test - it worked; I will leave to the reader to implement callback functions (along with SquareInt) with another types of arguments and supplement our test with such functions.