I understand why you might need generics, but I hate how Java ecosystem exploits it so much that, reading code becomes so difficult, because it was inherited from 10 levels of parent classes and in some cases Java Beans get created based on generic types and their parent classes are abstract classes.
[0] Other corporate messaging systems are available.
A poster child for this is k8s.
So I doubt the famous AbstractProxyBeanInterceptors are coming from an overuse of generics.
I have found Spring like magic with pre-processor macros, sed and awk, in the glory days of Yourdon Structured Method and enterprisy C code.
Most of them without any kind of generics.
This is not (yet) a thing in Go. Go does not have classes and inheritance. Interface is more like duck-typing.
I have yet to encounter a 3rd party library that uses generics that isn't a data structure library of some sort. The only person I have seen doing crazy things with generics is... me. In my own code, for the most part [1]. In general I don't even see generics that are so much as parameterized by a meaningful interface like "type BlahBlah[R io.Reader]"; generics are so often parameterized by "any" that a casual reader might be forgiven for thinking that that is all a Go generic can do. As is often the case, the most complicated generic type signatures are the one in the standard library and even they aren't really all that bad, it's really just about the "~" operator allowing support for "all types that are either 'string' or something of the form 'type X string'".
I see the occasional person complain about how generics have ruined Go by making them too complicated. I push back on them with basically the previous paragraph. None of them have ever replied to press their point any farther. I interpret this to mean that their experience with the feature is in fact the same. At this point if you're buried in super complicated generics it is almost certainly someone on your own team and you should tell them to stop.
Similarly for iterators, by the way. The way Go did iterators is a bit odd to implement them yourself. Won't deny that. But it does compile down very efficiently, and only matters to the people writing the iterator not the consumer. Anyone who claims it has wrecked the language is invited to explain to me why it is that they are writing so many dozens of iterators all the time first, because as near as I can tell they aren't getting that from 3rd party libraries or the standard library.
[1]: The exception being https://github.com/thejerf/mtmap if you want to see something that I did find a use for, and you may have a use for, but is not something I would suggest using all the time. Very specific use case I had.
IMHO these all go back to design compromises due to the desire to make generics optional - but now, over two decades later, are Java generics really optional?
The ten levels of parent classes is its own issue with Java - with interfaces and class inheritance being a good half of your mechanisms for abstractions in the code base, with adapters making up the other half.
In the Go codebases I have worked on, generics were not overused IMHO. It did allow us to get rid of some type specific helper functions and made collection based operations like sorting a lot nicer to work with.
What if the compiler generated both vtable and monomorphized variants for each method? Most calls would use the static version but places that need it like interface generics call the virtual method.
Note that despite having much stronger static semantics and much weaker reflection than Go Rust has the exact same limitation: a trait method with a non-lifetime generic parameter is not dyn-compatible. C++ is the same, a template member function (generic method) can’t be virtual (dynamically dispatched).
> However, Go’s interface system works at runtime. The specific type of a value passed into an interface parameter is resolved while the program runs.
Later when you pass this value around and assign it to places, sure, there may be a different type depending on where the interface value came from. But the construction of these values is very much static.
In this sense I wouldn't really call this "resolv[ing] while the program runs" unless you take a very broad sense of "resolution."
Whereas the runtime resolution of operations performed through an interface (dynamic dispatch) is the primary purpose of and use care for interfaces, with the dynamic resolution of wrapped types (type erasure / downcasts / type switches) is the secondary one.
They also give you a limited form of overloading. Without methods or overloading, you end up in the situation that C and Scheme are in where every operation on a data structure has to redundantly have the data structure in its name like:
list_clear(my_list);
queue_clear(my_queue);
map_clear(my_map);Go doesn't have overloading by parameter list signature. But you can have methods with the same name defined on different types, so there is a sort of overloading or namespacing based on the receiver type. Methods give you that.
SICP: 2.5 Systems with Generic Operations
Well in C at least we now have this:
#define clear(s) _Generic((s) \
,struct list: list_clear \
,struct queue: queue_clear \
,struct map: map_clear \
)(s)
clear(my_map);
clear(my_list);
clear(my_queue);
...although it turns out the other nice thing about methods is automatic namespacing.And everyone gets to invent their own vtable implementation since the 1980's.