Rendered at 23:27:26 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
throwaw12 1 days ago [-]
people working actively in "modern" Go codebases with generics, is it becoming like a Java/Spring kind of codebase?
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.
dwaite 41 minutes ago [-]
Java has its own problems with generics, such as the near-lack of runtime representation or the lack of a This/Self type, as well as limited variance, leading to signatures like `public abstract class Enum<E extends Enum<E>>`.
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.
mrkeen 17 hours ago [-]
Neither Go nor Java's generics is rich enough for the problems you describe. They give you the means to turn IntList into List<I>, but not into <L>Int or <L><I>.
So I doubt the famous AbstractProxyBeanInterceptors are coming from an overuse of generics.
pjmlp 10 hours ago [-]
The irony is that while people bash on Java, Objective-C manages to have even longer names.
throw-the-towel 1 days ago [-]
I remember my coworkers having managed to write Java in pre-generics Go. The code had functions wrapped in functions wrapped in yet more functions, and interfaces only ever implemented by one type to replace the generics abuse. Life finds a way.
zimpenfish 1 days ago [-]
Yeah, Go people were always been perfectly capable of creating abominational messes before generics arrived. Once you've looked at an interface referencing an interface referencing an interface referencing an interface which is only ever instantiated by one (1) type, you realise that all hope has been lost and your only recourse is blatant sarcasm, pointed replies on Slack[0] and PRs, and finding the perpetrators after dark with a big sack of flour.
[0] Other corporate messaging systems are available.
pstuart 10 hours ago [-]
> Go people were always been perfectly capable of creating abominational messes before generics arrived
A poster child for this is k8s.
bbkane 1 days ago [-]
Entropy finds a way :)
pjmlp 1 days ago [-]
People like to blame Java for enterprise code, except they forget enterprise has been writing enterprisy code since Fortran, COBOL and Lisp days.
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.
masklinn 12 hours ago [-]
Having had the displeasure of working with struts, enterprise definitely never needed generics to enterprise.
pjmlp 10 hours ago [-]
I guess you missed all the nice xBase, FoxPro, Clipper, COBOL, Smalltalk, C, C++ , 4GL frameworks that predated Struts.
Most of them without any kind of generics.
masklinn 9 hours ago [-]
I never used most of these at all, and the rest I never worked with in an enterprisey context, so I can not talk about those confluences from personal experience.
riobard 1 days ago [-]
> inherited from 10 levels of parent classes
This is not (yet) a thing in Go. Go does not have classes and inheritance. Interface is more like duck-typing.
Zanfa 1 days ago [-]
But you can do equally horrible inheritance-y things with embedded structs.
metadat 1 days ago [-]
Yes, though it's non-idiomatic and in my experience, rare to encounter in the wild for anything you'd actually want to use.
saturn_vk 1 days ago [-]
That’s composition, and it’s not really the same as inheritance
EspressoGPT 1 days ago [-]
That's right, embedded structs in Go are far less common than inheritance in Java though.
jerf 1 days ago [-]
"people working actively in "modern" Go codebases with generics, is it becoming like a Java/Spring kind of codebase?"
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.
zbentley 1 days ago [-]
I’ve found generics useful at handling database-backed objects at the write boundary. Since the layout of those objects is generally pretty important, having middleware-ish things that handle interface/supertypes but return generic concrete types saves on a lot of reflection and casting.
eberkund 1 days ago [-]
I think Go is more commonly used in modern microservice architectures rather than in older monolithic architectures where the kind of Java code you're describing happens. Might have some impact on the level of complexity that can get out of hand.
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.
saturn_vk 1 days ago [-]
I’ve written generic code once or twice per year. Usage through libraries is higher, though it’s not very visible.
nicoburns 1 days ago [-]
Rust has had generics for 10 years and doesn't have this problem. I think it's more a culture problem in Java than anything (and arguably a historical one).
cryptos 10 hours ago [-]
And in the end it might be a culture problem in the contexts Java is used in.
ad_hockey 1 days ago [-]
I haven't seen that in the large codebase at work, for what it's worth. The thing that's surprised me about generics in Go is how rarely they're needed in practice. Very nice to have in the right place, but the vast majority of our source files still get along fine with plain old typed maps and slices.
vrosas 1 days ago [-]
I have been a full-time go developer since 2015 and I have quite literally never written a line of generic go code. I can't even recall seeing any written at any {job} since they've been introduced...
tancop 1 days ago [-]
> Go’s interface system works at runtime. The specific type of a value passed into an interface parameter is resolved while the program runs. This dynamic dispatch clashes with generics being resolved at compile time.
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.
masklinn 18 hours ago [-]
The problem is that your vtable would have to have a monomorphised copy of the generic method for each possible instance, this requires whole program analysis just to generate the interface — and even then I would not be surprised if you could generate calls which the compiler was not able to relate to the interface — and then you get to the potential size explosion of your vtable as it has to hold an unbounded number of methods.
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).
saturn_vk 1 days ago [-]
vtables still need types, and a generic method that is not yet instantiated will not have them
dekdrop 22 hours ago [-]
TIL
> 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.
mknyszek 21 hours ago [-]
There's a little more nuance to this. The compiler knows the static type of any value placed into an interface value, and emits code to place a concrete type pointer (or itab pointer) into the interface value. You can see this in the disassembly.
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."
masklinn 19 hours ago [-]
The construction of interface values is a very shallow mean and essentially irrelevant to the need for interfaces.
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.
DrCiphers 1 days ago [-]
So Go is becoming Java?
saturn_vk 1 days ago [-]
Absolutely. If you’ve never used Java or Go
bmurphy1976 1 days ago [-]
Maybe. It's been what I wished Java was since the beginning, personally.
1 days ago [-]
EdSchouten 1 days ago [-]
Go is often thought of as a successor of C. C doesn't have methods, only global functions. From my perspective, Go added methods primarily so that you can use them in combination with interfaces. Given that interfaces don't support generic methods, I'm personally not convinced that this feature was worth adding.
munificent 1 days ago [-]
> From my perspective, Go added methods primarily so that you can use them in combination with interfaces.
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:
In Go you would methods for that: my_list.Clear(), my_queue.Clear() and my_map.Clear(). Now you can define a Clearer interface, which has only the Clear method. That allows you to write a function clearAndLog(item Clearer) and it will work with the list, queue and map.
munificent 9 hours ago [-]
Yes, that's my point.
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.
...although it turns out the other nice thing about methods is automatic namespacing.
14 hours ago [-]
pjmlp 1 days ago [-]
Well, C has a kind of pseudo generics since C11.
And everyone gets to invent their own vtable implementation since the 1980's.
etse 1 days ago [-]
What was the reason for interfaces having to work at runtime?
duskwuff 21 hours ago [-]
The methods in an interface can be implemented by many different types, and it's often hard or impossible to determine which of those types will be passed in to a function. For example, the io.Reader interface is implemented by many different stream-like types, and functions which accept io.Reader arguments generally can't make assumptions about which of those types they'll get.
masklinn 19 hours ago [-]
That dynamic dispatch and type erasure are literally the purpose of interfaces?
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.
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.
So I doubt the famous AbstractProxyBeanInterceptors are coming from an overuse of generics.
[0] Other corporate messaging systems are available.
A poster child for this is k8s.
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.
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:
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
https://sarabander.github.io/sicp/html/2_002e5.xhtml
Well in C at least we now have this:
...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.