Go and the absence of generics
By: Danilo Oliveira
Tags: Generics, Go

As a Java programmer with some experience in C# and Scala, I was always skeptical about how good a statically typed language can be without generics. Some users are glad Go does not have generics, because it would be against the core values of the language (simplicity and minimalism). Other users affirm having generics wouldn’t be a bad thing to have, but it’s completely okay to live without them. With this skepticism in mind, I learned the language so I could do some criticism and avoid getting debunked for not knowing the language. Just kidding, the language is useful on its own and fun to learn, after doing some research I decided it would be worthy of learning.

Java without generics was horrible. Dealing with general purpose data structures (list, maps, and trees) or general purpose algorithms (sort, find) implied using unsafe code with instance-of and typecast. I understand Go is different because it has maps and slices baked into the core of the language, therefore using Go does not feel like using Java 1.4. It is not entirely true saying that Go does not have generics: functions like make, copy, and append, are generic: they work with any list of any type. Yet you are limited to use what is provided for you, you cannot (yet) create your own generic code.

How comfortable is it to program with such language? Let’s find out with some examples.

Generic set type

After maps and lists, sets are the third best data structure we have at disposition when writing algorithms (this is personal opinion). Go does not provide sets as first-class citizens like maps and slices. This is okay since we have maps, you just need to use a map and place the elements as the keys. The values can be either a constant value like true or 1. Checking if an element belongs to the set can be done via the _, ok := map[element] idiom.

Doing union, intersection and other operations must be done manually, but 99% of the time when I use sets, I just add and check if elements belong to the set. So far, so good. For me, it is okay to use maps as sets.

Generic collections other than slices and maps

Lists, maps, and sets alone can provide me everything I need in terms of data structures for my algorithms. Yet, some algorithms require more specialized data structures like ordered sets/maps or priority queues. If Go does not provide them as first class citizens like maps and slices, and generics are not included in the language, what should we do instead?

Well, in this case, we are unlucky. Either you write the implementation focusing on a specific type, or you use the wildcard type, interface{}. Using the first option is quite limiting since it restrains the usability of the structure, and the second option is basically Java 1.4.

You can see this implementation of a treeset follows the second approach:

https://godoc.org/github.com/emirpasic/gods/sets/treeset

Sorting and shuffling slices

“How do I sort a slice in go?”

“How do I shuffle a slice in go?”

I submitted those questions to Google when I was trying to get some small program done, while I was learning the fundamentals of the language. Before I looked at the answer, I thought:

“Maybe they will have a generic sort implementation for primitive values and strings, but for structs, they will rely on the interface{} type, like the qsort function does in C using void* pointers”

The answer was not what I was expecting, it was quite surprising to see how it is done. By having good support for closures, sorting an slice can be done in a slightly better way than C (from this tutorial):

family := []struct {

Name string

Age int

}{

{"Alice", 23},

{"David", 2},

{"Eve", 2},

{"Bob", 25},

}



// Sort by age, keeping original order or equal elements.

sort.SliceStable(family, func(i, j int) bool {

return family[i].Age < family[j].Age

})

This is a very smart trick: to rely on a closure that uses the index of the positions as parameters and the original list as an up-value/lexical variable from the closure. This is fair enough for me, it’s not a bad way to sort lists with a custom comparator.

Shuffling slices, however, looks uglier:

a := []int{1, 2, 3, 4, 5, 6, 7, 8}

rand.Seed(time.Now().UnixNano())

rand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })

It is necessary to give as an argument a lambda for swapping the elements of two different positions. It does not feel pleasant to write and read, in my opinion, since with any other language (dynamic or static with generics) we can do something like “list.shuffle() or Collections.shuffle(list)”.

Removing elements from the middle of a slice

Appending values to a slice is quite easy with the append function, however, there is no function to remove an element from a slice. Looking for a way on how do this, I found the following answer:

func remove(slice []int, i int) []int {

copy(slice[i:], slice[i+1:])

return slice[:len(slice)-1]

}

You need to resort to slice trickery and use the copy function just to remove an element from the middle of a slice. Once again the language does not feel pleasant to use, knowing that in any other language I can just type “list.remove(i)” and get the job done. Removing an element from the first or last position is slightly easier:

slice = slice[1:len(slice)]

slice = slice[:len(slice)-1]

But don’t forget to set the removed element to nil if it is a pointer, so the GC can collect it.

Conclusions

Go is a very intriguing language. In the time where all other languages are getting more bloated and introducing more aspects of declarative programming, Go is making a tremendous success despite being as imperative as Pascal or C (its design is directly influenced by both languages). I don’t deny it has many right points and that it is a very capable language, however, the lack of generics can be a bit frustrating.

I’ll continue to learn and use the language, but I still prefer Java, C# and Scala. If I wrote any incorrect argument, or if some code example could be better done in a better way, I would appreciate to know.