Go Away Go 1 — My Container Library

Dr. Timm Felden
3 min readMay 15, 2023

--

Recently, I tried to fix some issues with the Go code base I work on. The Go “container library” makes it really hard to answer questions like do these two arrays contain the same elements if I do not care about order. Crazy operations like that are implemented by Go programmers by copying code around and changing types and maybe adding stuff here and there. Because, you might have experienced this already, crazy stuff like map is also not part of the Go standard library.

I have experienced this with Java, too. So, the Tyr compiler has a class ᚠ that has several such functions. You can write ᚠ.map(array, fun) and get an array as a result. If you think that way, you know why I do it and why it makes me a hell lot more productive. Thus, I tried to implement one such library for Go.

The first thing you do is define a package name. I like ᚠ because it is actually a single keystroke on my keyboard and you don’t clash with anything because I’m likely the only person on the planet with such a keyboard layout. Does not matter. What actually matters is that ᚠ is a letter from an alphabet that has no real concept of capitalization. Go does and it is very strict about it, because adding keywords like public and private is too much overhead for an efficient language like Go. So far, so good.

Now, most container libraries have Array, ArrayBuffer, HashSet, HashMap. If one of them is missing, it’s likely the Array. Does not matter. Go does not have HashSets. They do not have sets at all. Why? Likely too crazy for them. Actually, the best way in go is to use a map with an empty struct as value type. That gets optimized to void?!? Ok, I didn’t expect it, but it is actually easy enough to find out. Likely, because every Go programmer on the planet had that problem after like the first thousand lines of code. So, a map function would be generic, meaning that it would operate on generic sets. Piece of cake, right? We need to define a generic set type, like

type set[T any] map[T]struct{}

The compiler won’t like it. Actually, there is no solution here. None. At. All. If you have a closer look at all the Go generics docs, you’ll notice that the map keys are never generics. Why? Because Go can’t represent them. Even now with Go 1.20, the compiler tells you “missing comparable constraint”. Actually, they just noticed. And fixed it. Right? Ok, what is this comparable? If you have some experience with programming languages, you might already be surprised, because the expected constraint name would be equivalence relation or *hashed* equivalence relation. I don’t want to dig too deep into math, so long story short, the comparable constraint is not what you need here. It, as the name suggests, allows you to compare things. With less and greater. In most languages, this HashedEQR is passed as type parameter. For instance in C++, there are separate functions passed as template arguments. This is nice, because it allows you to reuse the map implementation for maps that have string keys but ignore case. It simply works if you pass the correct hash and equals implementations. In Go, however, there is no such parameter. No such constraint. No interface that can be implemented to get things right. There is just copy & paste and all the issues you get from massive code cloning.

My attempt to fix this issue literally died in the first line of code, because there is no way to express a generic set in go. And even if there were a way, we would still be left with lack of static function overloading and missing operator overloading required to satisfy the comparable constraint. Because Go does not have generics. They have started to work on that subject. And they have a very very long road ahead of them.

--

--

Dr. Timm Felden

Programming language enthusiast for decades. Author of Tyr. Writes about types and programming languages.