Go generics proposal moves to “likely accept”
github.com
github.com
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.
I'm not so certain that that's the case, but it is an interesting position nonetheless.
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.
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 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.
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.
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.
> Why does Go not have generic types?
> 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.
> This remains an open issue.
https://web.archive.org/web/20091115211116/http://golang.org...
And per https://blog.golang.org/generics-proposal, they hope to get this into the 1.18 release.
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 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.
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)?
Used to be, a long time ago.
a < b
can be greedily interpreted as a comparison, despite the next character being a > a [ b
can be greedily interpreted as accessing a field in a map, despite the next character being a ]It may be easier to branch in one of those cases instead of the other.
Not at all familiar with the potential issues (if any) in go, but this is from experience in writing a few languages.
This line of code seems to say why is troublesome given Go's other syntax:
a, b = w < x, y > (z)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
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)
I've seen tons of golang code that would have been much simpler, and less error prone if the language had generics.
Some just want to avoid C++ templates and all those warts.
I think the consensus is that Go community want it done right, or not at all.
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.
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.
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.
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.
Anyone able to give me a simple example demonstrating why Go needs this?
I guess writing a numeric library, like Python's NumPy, is a lot more convenient using generics. But I've never tried...
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.
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).
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.
Or have I misunderstood something?
From the proposal:
> 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{}.
Further unification possible: https://github.com/golang/go/issues/33232
However I do agree this is making it somewhat confusing what is in a static and what is in a runtime position.
> we suggest a type constraint any that is equivalent to interface{}.
That's my point. They chose the keyword any to disambiguate the type constraint interface{} from the type interface{}.