Experimenting with generics in Go

Alexey Soshin
The Startup
Published in
3 min readJun 24, 2020
Photo by Louis Reed on Unsplash

On 16th of June the Go team has published an update on generics in Go. Alongside a very thorough design draft, that ditches “contracts” and adopts more conventional approach to typed parameters, came the ability to play with generics yourself online, using the new Go Playground.

Let’s see how to work with the new proposal, and what it promises for the Go language.

For the sake of the exercise we’ll implement a simple list of generics elements, that should have some basic higher order methods as well, such as Filter, Foreach and Map

We’ll start with the definition of our generic data structure:

type List(type T) struct {
elems []T
}

As you can see, we now are able to declare types after struct or interface name. Most languages use <> to denote a type, although Scala, for example, uses [] . Go uses (type) for the same purpose.

Having declared the type, we now can use it inside our struct to define slice of elements.

Translating this to Java, we would have something along those lines:

class MyList<T> {
private List<T> elements = new ArrayList<>();
}

Having defined our generic data structure, let’s implement Size , Get and Add on it.

Size is pretty trivial:

func (this *List(T)) Size() int {
return len(this.elems)
}

Note that we need to define that our method receiver is a generic type: List(T) , and not List , as we would do before.

Add is more interesting:

func (this *List(T)) Add(e T) *List(T) {
this.elems = append(this.elems, e)
return this
}

Here we return the struct we operate on to allow chaining:

list.Add("A").Add("B").Add("C")

Note that this is still type-safe:

var listOfNumbers List(int)
list.Add(1).Add(2).Add("A") // cannot convert "A" to int

Get returns element in a type-safe manner. No more casting and runtime panics.

func (this *List(T)) Get(i int) T {
return this.elems[i]
}

Here I omitted the range check entirely, but that’s for brevity.

Now, to the more interesting stuff.

Let’s check if we could have Filter , Foreach and Map in “Go2”.

Foreach is the simplest one:

func (this *List(T)) Foreach(f func(T)) {
for i := 0; i < this.Size(); i++ {
f(this.Get(i))
}
}

The function we invoke on each element of our List doesn’t return anything, and neither does our method.

Function we receive as an argument is also typed: f func(T)

Filter will return a new list, without mutation the original one:

func (this *List(T)) Filter(f func(T) bool) *List(T) {
var res List(T)
for i := 0; i < this.Size(); i++ {
e := this.Get(i)
if f(e) {
res.Add(e)
}
}
return &res
}

We’re using Size , Get and Add methods we defined earlier.

Finally, let’s implement Map. That’s were things are a bit more tricky:

func Map(type F, T)(list List(F), f func(F) T) *List(T) {
var res List(T)
for i := 0; i < list.Size(); i++ {
e := list.Get(i)
res.Add(f(e))
}
return &res
}

Wait, what happened? Well, in Java, we would define map() such as:

class MyList<T> {
public <R> MyList<R> map(Function<T, R> mapper) {
...
}
}

Note: I’m dropping the extends , super and wildcards here

Notice the <R> , generic that defined only on method, not on class.

But Go doesn’t support generics on methods at the moment. So, Map will have to stay a function with two generic parameters, and not a method.

Let’s see how it all comes together now:

func main() {
var listOfNumbers List(int)
for i := 1; i <= 10; i++ {
listOfNumbers.Add(i)
}
// Now we can have nice Filter and Foreach in Go!
listOfNumbers.Filter(func(i int) bool {
return i%2 == 0
}).Foreach(func(i int) {
fmt.Println(i)
})
// And Map is also not that bad
listOfStrings := Map(listOfNumbers, func(i int) string {
s := strconv.Itoa(i)
return s + s
})
// The most important part - it's generic!
listOfStrings.Foreach(func(s string) {
fmt.Println(s)
})
}

Conclusions

Must admit, that I’m quite happy with the current proposal.

It is certainly not the most powerful type system in the world (Scala can rest in peace for another millennia at the very least), but it’s powerful enough for 80% of the use cases, and it’s familiar enough, unlike previous contracts proposal.

So, if you’re working with Go anyway, that’s definitely something to be looking forward to.

Full example is available here: https://go2goplay.golang.org/p/o6okRdyuJ-3

--

--

Alexey Soshin
The Startup

Solutions Architect @Depop, author of “Kotlin Design Patterns and Best Practices” book and “Pragmatic System Design” course