It remains a selling point, and a concern for some Go developers. There's a lot of cognitive overhead in pervasively-parameterized codebases, and many developers would rather spend their cycles thinking about their algorithms or their problem domain rather than following extra layers of indirection.
I'm psyched to see generics arrive, but (I say this a lot) I write about as much Rust as I do Go these days, and there is definitely a cost to pervasive generics. I hope Go retains most of its original spirit, while at the same time making it easy for some people to write red-black trees or whatever.
> there is definitely a cost to pervasive generics
I've had a similar experience with TypeScript, whose type system is so breezy and flexible that it encourages levels of genericism I've not seen anywhere else, for better or worse. I think as type systems stop being a limiting factor, it's important we learn to moderate our own tendency to abstract. Go's ethos so far has been to enforce moderation of complexity at a language level, so it will be interesting to see what happens when generics open part of that up.
> I think as type systems stop being a limiting factor, it's important we learn to moderate our own tendency to abstract. Go's ethos so far has been to enforce moderation of complexity at a language level, so it will be interesting to see what happens when generics open part of that up.
I agree with this a lot. Relatedly, Go has this nice property that code tends to be pretty 'standard' regardless of who wrote it (facilitates reading and writing). I hope the additional expressiveness that comes with generics doesn't harm this 'standardization' property of the ecosystem.
> I've had a similar experience with TypeScript, whose type system is so breezy and flexible that it encourages levels of genericism I've not seen anywhere else, for better or worse
I wonder if this is a consequence of TypeScript's breezy generics or if it's because people are now adding types to the egregiously abstract things they were already doing in JS.
I would guess the driving factor is TypeScript's powerful inference capabilities. You can write generic functions all over the place, with multiple (sometimes interrelated!) type parameters, and you rarely have to pass explicit type arguments when calling anything. TypeScript will just figure it out. This removal of overhead when using complex types probably makes people less averse to creating them in the first place. Rust has a similar story.
> There's a lot of cognitive overhead in pervasively-parameterized codebases
The lack of generics leads to occasional excesses in code generation (-cough- k8s/client-go -cough-) which can be even worse. I suspect that observing this in practice lead the Go core team to be more receptive to generics proposals.
let's hope generics doesn't become a over(ab)used feature, that'd make the code harder to read and my experience harder to maintain, especially because when you need to fix something it takes a lot of time getting in context so you can actually write the code for the fix.
I mostly write Go these days and haven't found many use cases where generics are needed but there are definitely cases where they would make the code nicer.
I think you may have missed the thrust of the earlier comment (which is that some developers may elect to never use libraries that use generics, whereas some developers may prefer libraries that use generics, and it could cause a schism in the community without being a necessarily language-level-breaking change), but it's also worth noting that Go absolutely has broken tooling through a language update in the past (https://github.com/golang/go/issues/24661) and just generally does not have a particularly great history or coherent philosophy of good versioning practices and as such we should not just assume stability or backwards compatibility where there may not be any.
What's a major Go project that had to be ported, or maintained in two separate versions or branches, in order to target two different versions of the Go language itself? I don't think the Python comparison is particularly apt, whatever our other concerns about Go stability might be.
In a social/cultural sense, rather than a purely technical sense. Old Busted C++ vs Modern C++ might have been a better example.
Pre-generic and post-generic codebases will likely play awkwardly together, with lots of boxing between generics and interface{}. Folks who prefer generics will start to wrap or recreate popular existing libraries in the generic style.
It'll be a constant drag on everything.
Of course I may be wrong and the community might embrace the proposal whole-heartedly. But I won't hold my breath. Dismissing generics has been a gopher shibboleth for years and years.
When was that? The Go team has been working on generics since before 1.0, with numerous (failed) proposals along the way, and always said they would come when a suitable implementation was found; if ever found. That they wouldn’t compromise the language for a poor implementation of generics, just to have generics, may have been considered a selling feature?
> Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do.
> Generics are convenient but they come at a cost in complexity in the type system and run-time. We haven't yet found a design that gives value proportionate to the complexity, although we continue to think about it. Meanwhile, Go's built-in maps and slices, plus the ability to use the empty interface to construct containers (with explicit unboxing) mean in many cases it is possible to write code that does what generics would enable, if less smoothly.
I think much of the enthusiasm for Go comes from the fact that as a *compiled language*, it's easy to use and learn, and has a modern standard library. What attracts most of the developers is that they can deploy a binary with reasonable performance. That's why you see many PHP and Ruby developers loving it. I don't think an interpreted language with Go's design choices would be as popular. Therefore, I don't think adding Generics is going to harm.
Someone here more familiar with the process could maybe summarize the next several steps and the timeline within which we might expect to get a "go" binary that can compile generics.
I don't follow go language design very closely, but I'm curious about how this is going to be implemented, if it actually becomes accepted. What comes immediately to mind is boxing in Java (i.e. you can't have a generic of a primitive).
If go implements generics on top of boxing like Java did, isn't that basically just syntax sugar on top of `type T interface {}`?
For example, today I can write "generic" go code like so:
type T interface {}
func main() {
ints := []T {1,2,3,4}
intsum := Sum(ints, func(a T, b T) T {
return a.(int) + b.(int)
})
fmt.Println(intsum.(int))
}
func Sum(s []T, add func(T, T) T) T {
var sum = s[0]
for i := 1; i < len(s); i++ {
sum = add(sum, s[i])
}
return sum
}
The obvious issue is that the compiler defers type checking to runtime via the type assertions, so it's quite possible to panic on unexpected input; but then again the go compiler doesn't do anything against NPEs either. If the goal is to get the compiler to actually enforce generic types, wouldn't it run into exactly the same sort of problems Java 1.5(?) did in the 90s, or the compiler performance complaints that you get from languages like Rust?
I believe Go will be monomorphizing Sum[T] and generating machine code for Sum[int](s []int, add func(int, int) int) rather than passing a []interface{} and type checking at runtime (the way Java would for a type-erased sum method). Ada did this, though you had to explicitly instantiate a generic for each type(s) you intended to use (some early C++ compilers also required this).
Probably a naive question, but couldn't it do the same optimization for the code above? I believe Javascript engines like V8 do something of that sort.
A []int doesn’t have the same layout in memory as a []interface{}. You could copy the values to a new slice, but that’s expensive and it breaks anything relying on slices being mutable in place (this and covariance vs. contravariance is why Java won’t cast from int[] to Object[]).
Sure, but that's when we think in similar terms to what memory layout C code represents as opposed to what actually gets emitted by gcc -O3.
I definitely appreciate being able to mentally visualize what the memory layout of go code looks like, though I'm not sure that's actually an explicit goal of the language.
At this point, I fear that, with time, languages will end up adopting each and every paradigm, all of the syntactic sugars, and so on. I escaped perl for a reason.
My main programming language before Go was Perl. I enjoy both languages but Go stays in my head better than Perl. However, I still use all my favorite perlvars for one-liners when munging data.
It's interesting how "angle brackets vs. square brackets" is such an issue for many Go programmers commenting on the generics proposals (including earlier ones).
Maybe it is just revealing where those programmers are coming from. Java and C++? Angle brackets! Eiffel, Scala? Square brackets!
Are there any parser considerations (like <Foo <Bar>> being an issue in C++, afair), or is it just taste (or Scala vs. C++ background)?
Yes, in fact I can point to two different cases related to usage of angled brackets for generics:
1) Typescript claims to be a superset of JS, but it isn't, precisely because of this. `a<b,c>(d)` parses as a call to `a(d)` in TS, but parses to two comparisons in JS
2) D explicitly chose `!(foo)` syntax for templates over the `<foo>` syntax in order to avoid the parsing ambiguities problem
For those who are against generics (in general, or with Go specifically), what's your particular rationale? I find myself unable to create an effective anti-generics argument in the same way I can make a pro-generics argument.
So far what I've seen here seem to be:
- You can already use interface{} for something similar to generics
- I don't want the language to become more complex
- I can't conceive of how it'll be done efficiently/effectively
- I don't, personally, see the value of generics (i.e., it wouldn't change any code I've ever written)
My understanding of the trade-offs have to do with primarily with devil-in-the-details stuff, i.e. how the feature actually gets implemented.
AFAIK, there are two major philosophies: implement boxed types (which Go kinda already has, via `interface {}` being implemented as tuple of (type, value), or very aggressive static analysis (like Rust).
The problem with the former is what you get in Java: can't make generics of primitives, and runtime perf issues with boxing/unboxing when you want to e.g. sort terabytes worth of ints.
Conversely, the problem w/ the Rust approach is that the cost is shifted over to the compiler, which has to figure out how to emit optimal primitive-handling code given that the entry point of a generic call tree might be very far away (or even unknown at compile time). This leads to slow compilation times.
For go specifically, I think there's some amount of worry wrt overlapping features (given how go does interfaces) and the subsequent loss of clarity, given that go falls closer to the one-way-of-doing-things side of the scale.
All of these concerns are of the death-by-thousand-paper-cuts variety: can I live with a bit slower compilation times? Yes. Until it's so slow that I can't. Will I love generic collections? Yes. Until I'm neck-deep in a J2EE-like over-architected mess. Etc. Recall that go comes from Google, where minimizing the amount of crap in a billion LOC codebase is considered very important, so concerns like trading off simplicity and compiler speed for other benefits tend to be thought in terms of how the negatives look in the absolute worst case scenario.
My Go is very rusty so I was spending some time refreshing my memory on the empty interface.
As I understand it, in the worst case that Go's generics are implemented with boxed types and a pretty wrapper around the empty interface, you end up with equivalent performance to today but with clearer code (within functions, function declarations becoming somewhat more complicated but not excessively so).
But in the best case, this information being given to the compiler will give you clearer code (same qualifications as previous paragraph) and better performance as run-time type verification is moved to compile-time.
I suppose if you never use the existing workaround (empty interfaces), then there's no benefit to you in having generics available. But if you do, it seems to me that you're going to get some benefit.
Well, I don't think the feedback of people that don't use (or, more specifically, who don't feel the pain of not having) generics is all that relevant. In my mind, the trade-off is between potential runtime performance improvements via monomorphization vs the compile time cost of figuring out how to emit said code.
I also think there's an important consideration in terms of community and ecosystem. There are plenty of examples elsewhere where splitting on some core aspect of a language had some long lasting damaging effects (python 2 vs 3, perl vs raku, D v1 phobos vs tango, etc). I don't necessarily anticipate that this would be the case here, but it'd be unfortunate if the community split into different go flavors based on similar-but-incompatible ways of doing similar types of tasks.
It seems that most people who don't want generics are not able to provide good arguments in support of them. Just hand wavy ones like "I never needed them" or "complexity", even though complexity is inherent in reality.
I've seen tons of golang code that would have been much simpler, and less error prone if the language had generics.
Done right how? Practically every way that generics can be done is already done (C++, Java, C#, and Zig cover practically all bases). It seems very hand wavy for some people to say they want it done right, as if there's a magical way that hasn't been invented yet that will find its way into golang, which is known for being a language from the 70s as far as features go.
In a way promoting non-convoluted code. It's a property of the language which is like a side effect. Kind of reminds me of Ruby except Ruby was too dynamic to achieve it. C# is very readable too.
> I don't want the language to become more complex
Is the biggest reason for me. If I were the only one using a language I wouldn't care. But working on a team and having to deal with other people's towers of abstraction is awful. Without generics I wouldn't have to deal with it.
It depends on how you define "need". Golang could be replaced with a notation for NAND gates and still be able to "do" everything it does now.
Yesterday I was in a pairing interview where I debugged and sped up some code. All-in-all I wound up with about 200 lines or so of Golang, including a lot of "yes, another loop" code. All I was doing, really, was mapping, forking and joining.
Meanwhile in Java, I could have gotten most of this in about 20 lines withJava 8 Streams and .parallel(). Generics make that tractable.
No, I had chosen Go for that problem. My reasoning was that most of the code I've read or written in the past 2 years has been Go, so it seemed like I should focus on using a language where I was warmest.
But in retrospect I would have preferred Java. That's my fault.
If you did, I wonder if you would regret choosing Java over Go for other reasons. I tend to find that no matter what language I choose, there's some parts of the code that I would prefer to be able to write in a different language.
> I tend to find that no matter what language I choose, there's some parts of the code that I would prefer to be able to write in a different language.
There is no final nirvana language. That is the awful truth of existence.
Perhaps their existing codebase is in Go and they need to use a utility. Perhaps they really like the performance of Go compared to Java. Perhaps they can't just use Java because of inertia or that no one else knows Java. Languages have warts, and wanting to remove one is not a bad idea.
I've seen so many abuses of interface{} in packages, especially for containers, that anything that can alleviate this problem is a step in the right direction. Even when they are not strictly necessary I expect generics to improve a number of packages. Then there is also the potential performance benefit, since generics should be strictly compile-time features - at least I hope that's the case for Go, I haven't checked the details of the proposal.
Containers, operations on streams (map, filter, etc.), optional types (although those need tagged unions, something too advanced for golang), better concurrency handling (e.g. Task types), etc.
This is what I assumed generics were for. I haven’t been paying attention to the proposals but given the existing golang generics don’t support proper variance should I expect it with the user defined variance?
I wrote an Entity-Container system that really could've benefitted from a generic Get function. Instead I had to write on for each entity-type, even though they were the same except for different types.
Most places where the empty's interface is used now could benefit from it. Eg. the json package could state it always uses float64 for numbers (except when you tell it to use Number).
One example would be to return a single value from a method containing Either the value you want, or the error. That way you could chain a bunch of method calls together and just check for errors at the end instead of checking after each method call.
I don't know why "having generics" is regarded as an end unto itself, or even a particularly important feature. The more experienced I've become as a developer, the more I realize that the things that matter tend to be tooling, ecosystem, learning curve, documentation, standards, performance, etc. The things that tend not to matter so much are type system particulars. If people can make crazy money with dynamically typed languages, having a type system that provides static guarantees for 95% of use cases (beyond which you have to opt into dynamic typing via interface{}) is honestly fine. It's not elegant, but it gets the job done for a huge swath of software. Generics are nice to have, but I think people blow them wildly out of proportion.
EDIT: My implicit criterion for programming languages is the extent to which they facilitate software development, not some pursuit of elegance unto itself (although I fully empathize with the latter). These two things are not particularly congruent, but I often get the vibe that much criticism of Go assumes that it exists to be elegant instead of practical.
Cuts down on code and eases refactoring. Makes it faster to go from my mind to my code! (Once you get past the syntactical reading bump.) I'm really looking forward to really cleaning up some database and report code in particular. Bummed we won't see it in Go until at least 2022 though.
No doubt, but again we're talking about a single digit percentage of code paths, and again there are entire blockbuster applications written without any static typing at all. It'll be a nice feature for sure, but it's not like world-class software wasn't written in Go before nor will Go+generics be a "Java-killer".
Fully agree that generic type system is nicer than code generation; however, code generation thankfully isn't the only alternative nor even the best. Rather, as previously mentioned, we can use `interface{}` which is roughly the same as using dynamic types for the relevant bit of code. If people can make blockbuster apps with dynamically typed languages (100% of code paths are dynamically typed), it's really not a big deal that Go makes you drop into dynamic typing for <5% of code paths.
Having genetics as a good option is great. Using interface{} works too, but is a kludge if you have to do it alot. You lose performance to reflection and gain runtime errors that are hard to handle. It's not the best part of Go, far from it.
Codegen can work around weakness in a language, but the key is a build system that generates code, compiles it, and then throws it away. Checking it in is a mistake, because you have to start reviewing it if there’s any possibility of editing it.
Has Go's interface{} single-handedly confused a generation of software developers, to the extent that they just couldn't understand the language? Is this what happens when languages reach an escape velocity of userbase?
That heuristic seems to be changing somewhat, depending on how and where you use those specific characters. It sounds like they plan to make `interface{}` the same as `any` in the type parameters.
This makes sense if you forget anything you know about what an empty interface's implications were before, I think. Though I assume all the old reflection tricks are still valid, and potentially dangerous.
I didn't read all the discussion, but I wonder if someone is thinking about detecting cases of existing `interface{}` and helping lift it to a generic constraint when possible.
Thank you for articulating my snark, that's what I was referring to. Had it been called and treated as `any` then it would have made Go much easier and clearer to use.
I also haven't read most the discussion, but I'm kinda hoping golint / go vet disallow using the reflection stuff on variables with type parameters. A lot of the design of generics is intended to remove runtime errors (and C++-esque) compile-time errors that require you to inspect the body of a function instead of the function type). Casting a generic would go against that.
These are two entirely different concepts: `any` is a compile-time construct, `interface{}` is a run-time construct. At any specific callsite, `any` resolves into one specific type that gets passed at that specific callsite. Buy `interface{}` can hold values of different types at the same callsite.
> However, it‘s tedious to have to write interface{} every time you write a generic function that doesn’t impose constraints on its type parameters. So in this design we suggest a type constraint any that is equivalent to interface{}.