Looks like a big step in the right direction. The biggest pain is that methods cannot contain additional types, which prevents the definition of generic containers with a map method like
if you want to see what `Result(T)` and `Option(T)` look like under this proposal, check out a very stupid implementation here https://go2goplay.golang.org/p/dYth-AQ0Fru It's definitely more annoying.
I think it's interesting to observe that using the Result type is really not that much different from using a multiple return value - it's actually worse in some ways because you end up using "Unwrap" which can panic.
I like generics for collections but that is about it. I've seen "gifted" programmers turn everything into generic classes and functions which then makes it very difficult for anyone else to figure out what is going on.
One reason I like go is it doesn't have all the bells and whistles that give too many choices.
Here's some generic code I wrote in Rust recently. I had two unrelated iterators/collections and I needed to flatten and sort them into a single buffer.
Now you could argue that this is just "generics for collections" but the way those iterator combinators are written make heavy use of generics/traits that aren't immediately applicable for collections. Those same combinator techniques can be applied to a whole host of other abstractions that allow for that kind of user code, but it's only possible if the type system empowers library authors to do it.
I've certainly encountered codebases that abused inheritance and templates/generics to the point of obfuscation but you can abuse anything really. Besides in my experience the worst offenders where in C++ where the meta-programming is effectively duck-typed. Trait-based generics like in Rust go a long way into making generic code readable since you're always aware of what meta-type you're working with exactly.
I definitely don't use generics if they can be avoided, and I think preemptive use of genericity "just in case" can lead to the situation you describe. If I'm not sure I'll really need generics I just start writing my code without them and refactor later on if I find that I actually need them.
But even if you only really care about generics for collections, that's still a massive use case. There's a wealth of custom and optimized collections implemented in third party crates in Rust. Generics make these third-party collections as easy and clean to use as first party ones (which are usually themselves implemented in pure Rust, with hardly any compiler magic). Being easily able to implement a generic Atomic type, a generic Mutex type etc... without compiler magic is pretty damn useful IMO.
class Result<T>
{
bool IsSuccess {get; set;}
string Message {get; set;}
T Data {get; set;}
}
On many occasions, I like using result types for defining a standard response for calls. It's typed and success / fail can be handled as a cross-cutting concern.
> which then makes it very difficult for anyone else to figure out what is going on
Or we can learn to read them. Just treat types like a first class value. You either assign names to types like you do to values, or you can assign a name to a function that returns a type, this being generics.
I expect after a flurry of initial excitement, the community will settle on some standards about what it is and is not good for that will tend to resemble "Go 1.0 + a few things" moreso than "A complete rewrite of everything ever done for Go to be in some new 'generic' style".
> I like generics for collections but that is about it.
What about algorithms (sorts, folds, etc) on those containers? I write a lot of numerical code. It sucks to do multiple maintenance for functions that work on arrays of floats, doubles, complex floats, and complex doubles. Templates/Generics are a huge win for me here. Some functions work nicely on integer types too.
At this point I'd like to summon to go-generics defense all the PHP and Javascript developers who assert unwaveringly "Bad language design doesn't cause bad code; bad programmers cause bad code."
And that's among the reasons it's been left out of Go. Go design was guided by experience working on large software systems; the risk with making a language too flexible is that developers begin building domain-specific metalanguages inside the language, and before you know it your monolingual codebase becomes a sliced-up fiefdom of various pieces with mutually-incompatible metasyntax that defeats the primary advantage of using one language: developers being able to transition from one piece of the software to another without in-depth retraining.
For enterprise-level programming (which is the environment Go grew up in), a language that's too flexible is a hindrance, because you can always pay for more eng-hours, but you can't necessarily afford smarter programmers.
The idea is that an ID is just an int under the hood, but ID<User> and ID<Post> are different types so you can’t accidentally pass in a user id where a post is is expected.
Now, this is just a simple example that probably won’t catch too many bugs, but you can do more useful things like have a phantom parameter to represent if the data is sanitized, and then make sure that only sanitized strings are displayed.
The go team's attempt at involving everyone in the priorities of the language has meant they lost focus on the wisdom of the original design. I spent 10 years writing go and I'm now expecting to have to maintain garbage go2 code as punishment for my experience. I wish they focused on making the language better at what it does, instead of making it look like other languages.
If you don't understand someone else's code, you can either tell them they stuff is too complicated or learn and understand better.
There can be a middle ground of course.
I'd like generics for concurrency constructs. Obvious ones like Mutex<T> but generics are necessary for a bunch of other constructs like QueueConsumer<T> where I just provide a function from T -> error and it will handle all the concurrent consumption implementation for me. And yes, that's almost just a chan T except for the timeouts and error handling and concurrency level, etc.
There is an underappreciated advantage to using generics in function signatures: they inform you about exactly which properties of your type a function is going to ignore (this is called parametricity: https://en.wikipedia.org/wiki/Parametricity)
For instance, if you have a function `f : Vec<a> -> SomeType`, the fact that `a` is a type variable and not a concrete type gives you a lot of information about `f` for free: namely that it will not use any properties of the type `a`, it cannot inspect values of that type at all. So essentially you already know, without even glancing at the implementation, that `f` can only inspect the structure of the vector, not its contents.
I like generics but I find that it is often best to start out writing a version which is not generic (i.e. explicitly only support usize or something) then make it generic after that version is written. As a side benefit, I find that this forces me to really think about if it should actually be generic or not. One time I was writing a small Datalog engine in Rust and was initially going to make it take in generic atoms. However, I ended up deciding after going through the above process that I could just use u64 identities and just store a one to one map from the actual atoms to u64 and keep the implementation simpler.
I agree with the sentiment that it is very easy to overuse genetics though there are scenarios where they are very useful.
For java / c#, in my experience, I've done that mistake because in both language the class declaration is very verbose. Then using generic is the only way to solve a problem which can only be solved by dynamic typing / variables.
In typescript I don't need generic too much / too complex, because the typing definition is more lax, and we can use dynamic in the very complex scenario.
Honestly as long as you learn when to use generics and when to not use them there are a lot of very useful ways to encode state/invariant into the type system.
But I also have seen the problem with overuse of generics and other "advanced" type system features first hand (in libraries but also done by myself before I knew better).
I've done this to one of my pet projects (thankfully unreleased). It just makes debugging/editing on the fly more difficult.
I'd love to unwind the mess. But that'll take days fixing I caused in minutes!
It's a big foot gun.
I cant help feeling it is a missed opportunity to add generics to Go this late. A mistake that is copied from earlier languages (C++, Java), a mistake similar to other mistakes Go chose not to solve at it's inception, like: having implicit nulls (C, C++, Java, JS, C#), lack of proper sum types (C, C++, Java, JS) and only one blessed concurrency model (JS).
While I think I get the reasons for these decision in theory, make a simple-to-fully-understand language that compiles blazingly fast, I still feel it's a pity (most) these issues where not addressed.
This looks like a good, minimal implementation of generics. It's mostly parameterized types. Go already had parameterized types, but only the built-ins - maps and channels. Like this.
var v chan int
Somewhat surprisingly, the new generic types use a different syntax:
var v Vector(int)
Do you now write
v := Vector(int)
or
v := make(Vector(int))
Not sure. The built-in generics now seem more special than they need to be. This is how languages acquire cruft.
The solution to parameterized types which reference built-in operators is similarly minimal. You can't define the built in operators for new types. That's probably a good thing. Operator overloading for non-mathematical purposes seldom ends well.
They stopped before the template system became Turing-complete (I think) which prevents people from getting carried away and trying to write programs that run at compile time.
Overall, code written with this should be readable. C++ templates have reached the "you are not supposed to understand this" point. Don't need to go there again.
Maybe it's because I'm a newcomer to Go, but I'm surprised by all of the shade being thrown in the comments here. I think the design doc presents a clean and simple way to implement generics in Go -- and therefore it's very much in keeping with the spirit Go. I especially like the constraints system. Constraining generic types via interfaces just feels "right" for the language. It leans on a familiar mechanism with just enough leverage.
I'm not without concerns, but I'm struck by conflicting thoughts: I share the worry that implementing generics will negatively affect Go program readability which Go developers (rightfully) covet; when you have a hammer, everything looks like a nail. And yet, I also worry that the Go community is at risk of analysis paralysis on iterating the language in meaningful ways; the novel and innovative spirit that created Go must be harnessed to propel it into the future, lest it dies on the vine.
Finally. Better late than never. I have to write a lot of Go code at $BIGCORP and keep reaching for generic helper functions like Keys or Map or Filter or Contains. This is going to make my code much more readable.
Unlike other commenters, I don't think it's too late to add generics to Go. I'm looking forward to this.
My only negative reaction is that throughout these proposals, I've not been a fan of the additional parentheses needed for this syntax. I can think of several syntaxes that would be more readable, potentially at the expense of verbosity.
I think that there are benefits when taking the time to gather feedback from people who actually use the language and make educated decisions with regards to language features instead of shoving them down in v1.
I'm usually a bit of a skeptic when it comes to Go, but this proposal surprised me; for the most part, or seems like this is exactly what I would want if I were a Go programmer. Being able to define both unbounded generic parameters and parameters bounded by interfaces is key, and while from skimming I'm not sure if it allows giving multiple interfaces as a bound, in practice this shouldn't be a huge issue due to how Go's interfaces are implicitly implemented (which would allow you to define a new interface that's the union of all the other interfaces you want to require, and all the types that implement all of them will automatically implement the new one). Interestingly, the proposal also defines what is essentially a union type interface, which I think is something that Go definitely could use. Although there are a couple of minor warts (e.g. not being able to have generic methods) and the syntax is not what I'd personally pick, overall I'm pretty impressed.
“A type parameter with the comparable constraint accepts as a type argument any comparable type. It permits the use of == and != with values of that type parameter”
I think that’s an unfortunate choice. Quite a few other languages use the term equatable for that, and have comparable for types that have =, ≤ and similar defined. Using comparable here closes the door for adding that later.
I also find it unfortunate that they chose
type SignedInteger interface {
type int, int8, int16, int32, int64
}
as that means other libraries cannot extend the set of types satisfying the constraint.
One couldn’t, for example, have one’s biginteger class reuse a generic gcd or lcm function.
The comment at the end is where I got the link to the new branch, but as an outsider, I don't have any good way to ask where the new code review is, so I'm leaving this comment here in hopes that a googler will see it and point me in the right direction.
Based on a link that's on the new branch's page, it might be CL 771577, but it says I don't have permission to view it, so I'm not sure.
In my mind, this generics flip-flop will do no good for the long term survival of Golang. One way to view it is as "listening to the community and changing" but I think a change this big signals a fracture more than an evolution.
Think about how much planning and foresight goes into making a programming language, especially one that comes out of a massive top-tier tech company like Google. They deliberately chose not to include generics and (from what I remember when I wrote Go code) spent time + effort explaining away the need for them. Now the decision is being reversed, and it signals to me that they don't know what they're doing with the language, long term. As a developer, why do I want to hitch my carts to a language whose core visions are very clearly not correct or what people want?
There's a tension in languages between making it easier to write many layers of libraries versus making it easier to write applications.
Languages that want libraries that go ten layers deep need to carefully manage things with sophisticated type systems and compilers that can cut through and inline all the abstractions so it still runs fast. Think rust, C++, haskell (all of which, not by coincidence, have long compile times).
Languages for writing applications and solving problems tend to have a flatter dependency tree and wider standard library (often including practical functionality like JSON parsers). Think erlang, Perl, PHP.
I used to think Go was in the latter category, but it seems to be getting pulled into the former category. Perhaps that's a reflection of Go developers using more layers of abstraction than they have in the past?
Currently I just want some form of genericity, regardless which one, but I fear we will go through yet another cycle of community feedback and by August 2021 still not there.
However not to be just negative, I will certainly take the opportunity to provide my positive feedback as well, so looking forward to play with go2go.
It looks like this will make Go a significantly more complex language.
Despite Go's success over the last 10+ years, have its designers concluded that the language is too simple? Is there no place for a simple statically-typed language like current versions of Go?
If not, then for those of us who particularly want a simple language, is dynamic typing the only option?
The only time I get frustrated by the lack of generics in go in trying to get keys from a map... I can't have a single `keys(map[string]T) []string` function.
Ada language had generics since the first 1983 standard. When C++ came about it introduced OOP features that Ada lacked and didn't get for another 12 years when 1995 standard was introduced. C++ users never missed generics for many years and the language became ubiquitous while Ada remained marginalized. What's interesting is that all strongly typed languages have now jumped on the generics bandwagon, which to me shows that being ahead of its time doesn't pay off.
Typical to Go verbose style, but even that is better than nothing (example from the doc)
s := []int{1, 2, 3}
floats := slices.Map(s, func(i int) float64 { return float64(i) })
Type inference for generic function arguments
This is a feature we are not suggesting now, but could consider for later versions of the language.
This is really sad, generics add a ton of complexity and get abused over and over in many codebases. Sure if you have a competent team then you can stop the bleeding, but it'll be all over the place in your dependencies.
Every developer who discovers generics has this urge to be clever and write things that are hard to read and maintain.
Reading a Go codebase on the other hand is really a pleasure, this is due to the simplicity and straight-to-the-point aspect of the language, as well as the compile times. I really think Go had found a no-bullshit niche that it catered really well to, and I'm scared of what Go post-generics will look like.
Are there any other languages like Go that have no plans to move to generics?
Agree that it adds complexity - nobody wants a repeat of Java enterprise apps from years ago. In the world of scientific computing and recommendation systems, it's a godsend. Writing your own sort function and your own min function gets old, and you're tempted to return to the world of Python and NumPy. This brings Go onto much better footing with some basic generics functionality.
"My late friend Alain Fournier once told me that he considered the lowest form of academic work to be taxonomy"
That's really unfortunate. I think a lot of the point of science (maybe even the entire point?) is to model the world and create taxonomies. Instead of being considered "busy work" or tedious, it should be held in the highest regard. Similar reasoning is why I think Rob Pike's opinion on generics is exactly wrong.
There's C. C11 technically has a form of generics, but it's only really useful for math functions. I think you're safe from ever having parameterized collection types in common use over there.
Go was already an oddball in not having generics when it came out, so I think it's unlikely that any new statically typed language will ever become popular. They're just too useful.
[+] [-] peter_l_downs|5 years ago|reply
But, as per https://go.googlesource.com/proposal/+/refs/heads/master/des... it looks like maybe they will allow additional type parameters in methods, which would be REALLY nice.
[+] [-] rogpeppe1|5 years ago|reply
I think it's interesting to observe that using the Result type is really not that much different from using a multiple return value - it's actually worse in some ways because you end up using "Unwrap" which can panic.
[+] [-] jasondclinton|5 years ago|reply
[+] [-] whateveracct|5 years ago|reply
I think type parameters of methods will definitely get added since it's so standard and if anything more confusing to not have them.
[+] [-] rb808|5 years ago|reply
One reason I like go is it doesn't have all the bells and whistles that give too many choices.
[+] [-] qppo|5 years ago|reply
[+] [-] simias|5 years ago|reply
I definitely don't use generics if they can be avoided, and I think preemptive use of genericity "just in case" can lead to the situation you describe. If I'm not sure I'll really need generics I just start writing my code without them and refactor later on if I find that I actually need them.
But even if you only really care about generics for collections, that's still a massive use case. There's a wealth of custom and optimized collections implemented in third party crates in Rust. Generics make these third-party collections as easy and clean to use as first party ones (which are usually themselves implemented in pure Rust, with hardly any compiler magic). Being easily able to implement a generic Atomic type, a generic Mutex type etc... without compiler magic is pretty damn useful IMO.
[+] [-] gokhan|5 years ago|reply
[+] [-] mixedCase|5 years ago|reply
Or we can learn to read them. Just treat types like a first class value. You either assign names to types like you do to values, or you can assign a name to a function that returns a type, this being generics.
[+] [-] jerf|5 years ago|reply
[+] [-] xscott|5 years ago|reply
What about algorithms (sorts, folds, etc) on those containers? I write a lot of numerical code. It sucks to do multiple maintenance for functions that work on arrays of floats, doubles, complex floats, and complex doubles. Templates/Generics are a huge win for me here. Some functions work nicely on integer types too.
[+] [-] libria|5 years ago|reply
[+] [-] shadowgovt|5 years ago|reply
For enterprise-level programming (which is the environment Go grew up in), a language that's too flexible is a hindrance, because you can always pay for more eng-hours, but you can't necessarily afford smarter programmers.
[+] [-] neilparikh|5 years ago|reply
Ex.
The idea is that an ID is just an int under the hood, but ID<User> and ID<Post> are different types so you can’t accidentally pass in a user id where a post is is expected.Now, this is just a simple example that probably won’t catch too many bugs, but you can do more useful things like have a phantom parameter to represent if the data is sanitized, and then make sure that only sanitized strings are displayed.
[+] [-] throwawaygo|5 years ago|reply
[+] [-] jdxcode|5 years ago|reply
[+] [-] ragebol|5 years ago|reply
[+] [-] phamilton|5 years ago|reply
[+] [-] ookdatnog|5 years ago|reply
For instance, if you have a function `f : Vec<a> -> SomeType`, the fact that `a` is a type variable and not a concrete type gives you a lot of information about `f` for free: namely that it will not use any properties of the type `a`, it cannot inspect values of that type at all. So essentially you already know, without even glancing at the implementation, that `f` can only inspect the structure of the vector, not its contents.
[+] [-] slaymaker1907|5 years ago|reply
I agree with the sentiment that it is very easy to overuse genetics though there are scenarios where they are very useful.
[+] [-] jasonhansel|5 years ago|reply
- Promises
- Mutexes like Rust's Mutex<T> (would be much nicer than sync.Mutex)
- swap functions, like swap(pointer to T, pointer to T)
- combinators and other higher-order functions
[+] [-] fendy3002|5 years ago|reply
In typescript I don't need generic too much / too complex, because the typing definition is more lax, and we can use dynamic in the very complex scenario.
I don't know which approach go is taking.
[+] [-] dathinab|5 years ago|reply
But I also have seen the problem with overuse of generics and other "advanced" type system features first hand (in libraries but also done by myself before I knew better).
[+] [-] Forge36|5 years ago|reply
[+] [-] gowld|5 years ago|reply
[+] [-] cies|5 years ago|reply
While I think I get the reasons for these decision in theory, make a simple-to-fully-understand language that compiles blazingly fast, I still feel it's a pity (most) these issues where not addressed.
[+] [-] Animats|5 years ago|reply
The solution to parameterized types which reference built-in operators is similarly minimal. You can't define the built in operators for new types. That's probably a good thing. Operator overloading for non-mathematical purposes seldom ends well.
They stopped before the template system became Turing-complete (I think) which prevents people from getting carried away and trying to write programs that run at compile time.
Overall, code written with this should be readable. C++ templates have reached the "you are not supposed to understand this" point. Don't need to go there again.
[+] [-] kevlar1818|5 years ago|reply
I'm not without concerns, but I'm struck by conflicting thoughts: I share the worry that implementing generics will negatively affect Go program readability which Go developers (rightfully) covet; when you have a hammer, everything looks like a nail. And yet, I also worry that the Go community is at risk of analysis paralysis on iterating the language in meaningful ways; the novel and innovative spirit that created Go must be harnessed to propel it into the future, lest it dies on the vine.
[+] [-] adamch|5 years ago|reply
[+] [-] conroy|5 years ago|reply
[+] [-] binwiederhier|5 years ago|reply
[+] [-] atombender|5 years ago|reply
My only negative reaction is that throughout these proposals, I've not been a fan of the additional parentheses needed for this syntax. I can think of several syntaxes that would be more readable, potentially at the expense of verbosity.
[+] [-] hu3|5 years ago|reply
I think that there are benefits when taking the time to gather feedback from people who actually use the language and make educated decisions with regards to language features instead of shoving them down in v1.
[+] [-] ainar-g|5 years ago|reply
https://go.googlesource.com/proposal/+/refs/heads/master/des...
[+] [-] saghm|5 years ago|reply
[+] [-] Someone|5 years ago|reply
I think that’s an unfortunate choice. Quite a few other languages use the term equatable for that, and have comparable for types that have =, ≤ and similar defined. Using comparable here closes the door for adding that later.
I also find it unfortunate that they chose
as that means other libraries cannot extend the set of types satisfying the constraint.One couldn’t, for example, have one’s biginteger class reuse a generic gcd or lcm function.
[+] [-] coder543|5 years ago|reply
I can't find the actual code review for this. It seems to be hidden/private still?
This was the previous code review: https://go-review.googlesource.com/c/go/+/187317
The comment at the end is where I got the link to the new branch, but as an outsider, I don't have any good way to ask where the new code review is, so I'm leaving this comment here in hopes that a googler will see it and point me in the right direction.
Based on a link that's on the new branch's page, it might be CL 771577, but it says I don't have permission to view it, so I'm not sure.
[+] [-] daenz|5 years ago|reply
Think about how much planning and foresight goes into making a programming language, especially one that comes out of a massive top-tier tech company like Google. They deliberately chose not to include generics and (from what I remember when I wrote Go code) spent time + effort explaining away the need for them. Now the decision is being reversed, and it signals to me that they don't know what they're doing with the language, long term. As a developer, why do I want to hitch my carts to a language whose core visions are very clearly not correct or what people want?
[+] [-] jeffdavis|5 years ago|reply
Languages that want libraries that go ten layers deep need to carefully manage things with sophisticated type systems and compilers that can cut through and inline all the abstractions so it still runs fast. Think rust, C++, haskell (all of which, not by coincidence, have long compile times).
Languages for writing applications and solving problems tend to have a flatter dependency tree and wider standard library (often including practical functionality like JSON parsers). Think erlang, Perl, PHP.
I used to think Go was in the latter category, but it seems to be getting pulled into the former category. Perhaps that's a reflection of Go developers using more layers of abstraction than they have in the past?
[+] [-] djhworld|5 years ago|reply
[+] [-] pjmlp|5 years ago|reply
However not to be just negative, I will certainly take the opportunity to provide my positive feedback as well, so looking forward to play with go2go.
[+] [-] renewiltord|5 years ago|reply
[+] [-] pansa2|5 years ago|reply
Despite Go's success over the last 10+ years, have its designers concluded that the language is too simple? Is there no place for a simple statically-typed language like current versions of Go?
If not, then for those of us who particularly want a simple language, is dynamic typing the only option?
[+] [-] rowanseymour|5 years ago|reply
[+] [-] cybervasi|5 years ago|reply
[+] [-] galkk|5 years ago|reply
[+] [-] ccktlmazeltov|5 years ago|reply
Every developer who discovers generics has this urge to be clever and write things that are hard to read and maintain.
Reading a Go codebase on the other hand is really a pleasure, this is due to the simplicity and straight-to-the-point aspect of the language, as well as the compile times. I really think Go had found a no-bullshit niche that it catered really well to, and I'm scared of what Go post-generics will look like.
Are there any other languages like Go that have no plans to move to generics?
PS: beautiful quote from rob pike: https://news.ycombinator.com/item?id=6821389
[+] [-] jaredtn|5 years ago|reply
[+] [-] typon|5 years ago|reply
That's really unfortunate. I think a lot of the point of science (maybe even the entire point?) is to model the world and create taxonomies. Instead of being considered "busy work" or tedious, it should be held in the highest regard. Similar reasoning is why I think Rob Pike's opinion on generics is exactly wrong.
[+] [-] djur|5 years ago|reply
Go was already an oddball in not having generics when it came out, so I think it's unlikely that any new statically typed language will ever become popular. They're just too useful.