While bouncing back & forth between Go, JavaScript and some Elixir, some patterns & thoughts emerged and please bear with me even if there are better solution to these samples; which will fulfil their task in picturing the intended concepts - so they are demonstrative pseudocode.

puzzle pieces

We need to apply some functionality/computation to sets of data in a uniform way. For example let’s write a map utility for slices, which will map/apply a function to all items in a slice of any kind and gives us a slice of the same length with items of another kind. How about:

func mapSlice(sliceLen int, mapFunc func(int)) {
	for i := 0; i < sliceLen; i++ {
		mapFunc(i)
	}
}

You do not see any slices here. Instead this function uses the one thing that all items in a slice share uniformly - their index - to tell the passed map function on which item it should work.

For instance we could take a slice of strings, convert them to float numbers and have a slice of floats like this:

func sample1() {
	source := []string{`1`, `2`, `3`}
	result := make([]float64, len(source))
	mapSlice(len(source), func(ix int) {
		str := source[ix]
		f64, _ := strconv.ParseFloat(str, 64)
		result[ix] = f64 + 0.1
	})
	log.Println(result)
}

As you see we’ve passed a function to our utility (mapSlice) which receives the index of the item in the source slice, then grab the item at that index from source, then converts it to a float number and puts it inside the result slice at the same index.

A uniform property in all members of a slice - their index - allowed us to perform some actions on those members in a uniform way. This utility (mapSlice) works on all kind of Go slices in a uniform way - kinda in a generic way.

The target does not even need to be a slice. We could write some code with same structure to calculate multiplication of all of it’s items:

func reduceSlice(sliceLen int, reduceFunc func(int)) {
	for i := 0; i < sliceLen; i++ {
		reduceFunc(i)
	}
}

Which is pretty much the same mapSlice utility - done just some renaming to convey the intention. And multiplication of all items would be:

func sample2() {
	source := []float64{1.01, 2, 3}
	var result float64 = 1
	reduceSlice(len(source), func(ix int) {
		result *= source[ix]
	})
	log.Println(result)
}

What our utilities have in common so far? They both have two parts, one does the extraction of needed data and the other part is a common functionality to get applied to that extracted data.

Let’s try it with some type other than slices, like a struct that has some fields including a string and an uint64 and we want to calculate the hash of those string fields and put that hash in related uint64 field of the same object.

func hash(slots func() (*string, *uint64), hashFunc func(*string, *uint64)) {
	hashFunc(slots())
}

Here we have an extractor function (slots) which selects out the necessary fields of the object and the functionality/computation (hashFunc) that we want to apply to these extracted parts/fields. What our utility does is composing these two actions/functions, like in:

func sample3() {
	var t1 struct {
		t1Name string
		h1     uint64
	}
	var t2 struct {
		t2Name string
		h2     uint64
	}
	var t3 struct {
		t3Name string
		h3     uint64
	}

	// dummy data
	{
		t1.t1Name = `A`
		t2.t2Name = `B`
		t3.t3Name = `C`
	}

	name1 := func() (*string, *uint64) { return &t1.t1Name, &t1.h1 }
	name2 := func() (*string, *uint64) { return &t2.t2Name, &t2.h2 }
	name3 := func() (*string, *uint64) { return &t3.t3Name, &t3.h3 }

	hashFunc := func(name *string, hx *uint64) {
		h := fnv.New64a()
		h.Write([]byte(*name))
		*hx = h.Sum64()
	}

	hash(name1, hashFunc)
	hash(name2, hashFunc)
	hash(name3, hashFunc)

	log.Println(t1, t2, t3)
}

Any type that provides a proper selection mechanism (the extractor part) can be used with this utility and the same computation/functionality in a uniform way.

where’s the generic?

From this point of view, generics are a form of function composition, a pair of functions, one for extracting needed parts, and one another, which applies some computation to them, a form of map/reduce. And of-course an engine that performs the map/reduce action or we could say more precisely it composes map functions and reduce functions in meaningful way.

The first part (extraction/map) can be done in Go nicely employing interfaces. In fact too nicely in that you do not even have to do it explicitly. It’s done in a statically duck-typed manner which implies implicity. It even allows you to define interfaces that are already implement in a package which is not under your control!

On the other hand the second part (reduce) is impossible to implement in an old school, generic manner - does not exists. Because implementing the second part needs the compiler to pin down the actual underlying type or as we saw, we have to adapt the map for each case to transform data into something that can be fed into the reduce.

what are types?

Are generics as some sort of executable type specification? What problems does generics solve?

Having a structure for some data seems insufficient to uniquely identify that data. For example in Elixir there are situations that we “tag” the data with an atom. Because we have some groups of data with identical structure that should not be considered as being the same (ok, not just this). Is that Meta Data? Even the structure is not a one to one equivalent to the data - it’s always an “expected structure”; as we think and solve problems and go on; an “intended meaning”.

And even the structure is some sort of tag/matadata, not the value itself. So what’s a type? I wish I could understand it better. Even in statically “typed” languages, in those that it seems that the type is carrying the data, we go through all sort of hassle to maim the type at the point that we use it because at that point we wish for another structure/type while what we already have at that point, came from and designed based on another concern/restriction like db models, not our intention at that point.

So, what’s a type really? Maybe Go comes up with a nice answer for this, by proposing a different generic type definition.