I should send this to rsc, but it's fairly easy to find examples where the lack of generics caused an opportunity cost.
(1) I started porting our high-performance, concurrent cuckoo hashing code to Go about 4 years ago. I quit. You can probably guess why from the comments at the top of the file about boxing things with interface{}. It just got slow and gross, to the point where libcuckoo-go was slower and more bloated than the integrated map type, just because of all the boxing: https://github.com/efficient/go-cuckoo/blob/master/cuckoo.go
(my research group created libcuckoo.)
Go 1.9 offers a native concurrent map type, four years after we looked at getting libcuckoo on go -- because fundamental containers like this really benefit from being type-safe and fast.
(2) I chose to very tightly restrict the initial set of operations we initially accepted into the TensorFlow Go API because there was no non-gross way that I could see to manipulate Tensor types without adding the syntactic equivalent of the bigint library, where everything was Tensor.This(a, b), and Tensor.That(z, q). https://github.com/tensorflow/tensorflow/pull/1237
and https://github.com/tensorflow/tensorflow/pull/1771
I love go, but the lack of generics simply causes me to look elsewhere for certain large classes of development and research. We need them.
All of Go's built-in pseudo-generic types (e.g. maps) require special support from the parser. I'm not sure if they plan on doing that for sync.Map as well, but this is clearly an area that could benefit from generics.
For (2), are you looking for overloading arithmetic operators (+, -, etc.)? Do people normally consider that under the umbrella of "generics"?
For (1), for curiosity sake, I tried benchmarking the cuckoo.go file you posted. I'm curious if these numbers are in line with what you found. The first test I did was a Rand test, Putting 10,000 random string values under random string keys, then Getting the same 10,000 keys, then Getting 5000 more random keys. The first line below uses default code you posted, which uses
type keytype string
type valuetype string
The second line specializes the code to just using the string type directly for keys and values. The third line uses interface{} instead for values, which is the code style HN doesn't like.
That third line shows the cost of casting to/from interface{}. The difference in the code was about as you might expect, with the "void" example needing a cast on each put and a cast on each get.
The 9.4ms result is using a version of your code hand-specialized for string key and uint32 values. The 10.4ms result uses interface{} for values.
I ran a sequential-insert test as well, with keys and values generated sequentially instead of randomly. The results are much the same, though the cost of casting to/from interface{} seems to get mostly lost in the noise.
I also tried some tests using uint32 as the key type. This int key specialized version is 3x faster than any of the string key versions, but it's not really a fair comparison... a small part of the code is specific to string keys (getinthash), so it's not as obvious what the generic equivalent of that function would be. The keys need to be Hashable or some such, not just interface{}. I'm also allocating and formatting random strings in one case, vs just picking random numbers in the other case.
I didn't find the interface{} casting in any of the above code too horribly "gross", just a bit ugly, but that's entirely subjective. Yes, all of the cuckoo versions seemed about 30% slower than the corresponding builtin map type, but my benchmark numbers don't show if that is the price of boxing, of supporting concurrency, or maybe function call overhead, or something else. I'm guessing you did more detailed benchmarks that point to the source of the slowdown.
One last comment: keytype and valuetype really need to be exported, don't they? I wasn't able to use the library without changing them to exported symbols.
> For (2), are you looking for overloading arithmetic operators (+, -, etc.)? Do people normally consider that under the umbrella of "generics"?
Overloading is usually not a "generics" concern in most mainstream languages[1]. If you do a "type class"/"Rust-style-traits" (or even multiclasses) thing then it sort of naturally takes a front seat. Incidentally, Haskell fucked this one up horribly in some respects, see the "Num" type class.
Of course, e.g. Java could theoretically add an interface called Addable to complement Comparable which would sort-of allow generic "+", but they've chosen to not opt for that. The fact that the "first parameter" is privileged is also a bit of a detriment to such syntax conveniences. (I can expand on this, but it's bascially because that for "X op Y" and "Y op X" to work, they both need to know of each other. This problem can be solved by type classes, but cannot be solved by overloading, AFAIK.)
[1] If you do have overloading from the outset (like Java, IIRC), then you're constrained because you need to be able to "pick the most specific overload" in a "generic" context.
(1) I started porting our high-performance, concurrent cuckoo hashing code to Go about 4 years ago. I quit. You can probably guess why from the comments at the top of the file about boxing things with interface{}. It just got slow and gross, to the point where libcuckoo-go was slower and more bloated than the integrated map type, just because of all the boxing: https://github.com/efficient/go-cuckoo/blob/master/cuckoo.go
(my research group created libcuckoo.)
Go 1.9 offers a native concurrent map type, four years after we looked at getting libcuckoo on go -- because fundamental containers like this really benefit from being type-safe and fast.
(2) I chose to very tightly restrict the initial set of operations we initially accepted into the TensorFlow Go API because there was no non-gross way that I could see to manipulate Tensor types without adding the syntactic equivalent of the bigint library, where everything was Tensor.This(a, b), and Tensor.That(z, q). https://github.com/tensorflow/tensorflow/pull/1237 and https://github.com/tensorflow/tensorflow/pull/1771
I love go, but the lack of generics simply causes me to look elsewhere for certain large classes of development and research. We need them.