Rather than going through this whole rigmarole of resisting adding generics too early because all existing implementations are bad, then slowly reinventing the wheel from scratch, then finally ending up with something pretty similar to one of those other languages anyway.
It’s OK not to make something completely new and different. It’d be useful to explicitly say which language(s) you’re borrowing from because you can then more clearly call out the differences and explain the reasons for them.
I'm also not sure why in OOP-land, generics are this crazy experimental weird feature, when in functional languages, people figured out how to implement parametric polymorphism (the original term for generics) in quite reasonable ways. I get that subtyping adds some complexity, but overall I don't understand why such a basic way to build abstractions is so controversial in (some) OOP languages. If anyone has some context on why this is more difficult to have in Java-like languages, I'd be curious to hear it.
For instance, if A < B (B extends A), What is the relationship betwern Array[A] and Array[B]?
If you are just reading the array, you would want Array[A] < Array[B]. If you are writing to the array, you would want Array[B]<Array[A]. If you are doing both, you want Array[A] to have no relation to Array[B].
This problem doesn't come up in ML style languages because they do not make use of inheritence.
One other factor is that functional languages typically are theory first, implementation second, while non-functional languages tend to more often have the language features follow whatever the implementation admits. I know for Go they fretted a lot about how you'd efficiently compile generics, which is not something that comes up when you're designing System F or whatever.
But I believe that subtyping has a massive impact on generics, particularly in OOP languages where people expect to use lots of subtypes. There are all of these new questions around not only bounded polymorphism but also variance and mutability and how inference works.
Even TypeScript, which is following a lot of the design decisions already established by C# with the same person behind both, is still kinda just meandering around the design space and continuing to make changes to the semantics in new versions.
It's not, it's an essential, basic feature.
If you tell me, for example, that Go generics are going to be like C# generics, then I have to be familiar with the full semantics of C# generics. Essentially you tell me that in order to understand X I have to understand Y first, that's not good. Consumers of your language are not, for the MOST part, PLT nerds.
Admittedly, the Rust async/await RFC and the Go contracts proposal both discuss prior art in sections towards the end, so they are actually similar in that respect. Maybe it’s really just a question of tone and messaging, and the particular discussions that tend to end up on HN.
Or you could research the very detailed discussion and documentation available on the topic of C# generics.
Presumably, by the time the feature ships, the golang.org/doc entry on generics will not consist of just 'lol, they are just like C# generics, docs.microsoft.com, chum'
I guess my complaint is that this talk, like all the other updates on their progress on generics, implies that Go generics exist in a vacuum, whereas in reality there’s a ton of prior art that could usefully be referenced.
Edit to add: this particular talk is focused on syntax details. Those aren’t unimportant but they’re a small part of the whole picture. As I commented on the detailed Contracts proposal, the decision to add contracts rather than simply using interfaces (as in Java and C#) seems significant but isn’t explained.
Reads more like suggesting to me. And, a helpful one.
This is basically how C++ was designed, and it turns out not to work very well; the [adjustments₁] for [feature₁] turn out to introduce not only unanticipated [problems₁] with [feature₁] itself but also new and previously unimagined [problems₂] with [feature₂]. So the Golang designers prefer to take a much more cautious approach than the Lumbergh approach you're suggesting. So far it seems to have worked out well — the language is not without its compromises, and it's substantially more complicated than it was at first, but it's a very reasonable compromise.
Part of the explicit goal stated by the go team is that generics must still feel like go. If you slapped java generics onto go it would not feel like go.
For the future of languages I appreciate the clean slate effort. Go has been about scoping out a use area sticking a leg there and coming up with something that works well there. At the same time I dont think there will be anything revolutionary, just something that seems simple and compact. I wish them great success and us a short wait.
Had to do an application that dealt with 4-5 forms (can't remember). Like 300 fields on the bigger ones, smaller ones 50ish. I started out on one that was mid-200. I'm coding the way I was taught in school: object set object properties based on txtWhatever.Text. Like 240ish times. I can't remember how many lines of code were in this code behind, but it was substantial.
I turn it in, it works. Go me, let me go on to the next one. The senior guy on the team does one of the smaller forms. But he uses generics and reflection to basically iterate over all of the fields on the form and sets them to the ephemeral properties on this generic object in like 8-9 lines of code. Then there was the rendering code that was vaguely similar.
Added bonus: 99% of his code behind could be copy and pasted over to the newer forms and handle all of the work related to getting/setting form values. After the code proved to work for a few weeks, replaced the massive code behind with call to his code (passing in the form object and the generic to be set and used later) with the same result and one area to manage code and it isn't an insanely large code behind file (got better with not one filing everything in time too).
Not saying generics and/or reflection are a silver bullet (I don't feel they are. Rarely so do they end up being the thing I go for), but it was definitely eye opening that straying from "see spot run" code could be advantageous. And that I wasn't "good to go" already.
That said, I do want generics to come to Go, just... with an emphasis on what Go is, not just "here, let's replicate X's generics in Go". I am interested in the contracts implementation.
The team I work in use generics all the time and I'm not sure what complexity you are referring to. Care to elaborate? Is it some edge cases or are you talking about from a compiler perspective or something else?
To me, not having generics is like saying let's skip handling bools and just store them in strings as "true" or "false". It's such a weird thing from my point of view.
I know how to use them. They are valuable to me.
There was a story I came across once. It went something like this:
The proponents of every gizmo think their gizmo is superior to the others, because it's got these useful features that the others lack, and isn't encumbered by the weird useless stuff those other languages have.
If only they spent time to understand those "weird useless features"...
https://hn.algolia.com/?query=generics&sort=byPopularity&pre...
Go 3: Why HKT?
Go 4: Why homoiconicity?
Go 5: Why uniqueness and borrowing?If you have some type A, it has the kind * . If you have some function from A -> B, it has the kind * -> * .
However, I think that the people we should be listening most are the ones developing huge projects in Go, like Kubernetes. Would having generics with this new contracts thing make it easier to develop and maintain e.g. Kubernetes? I'm truly curious.
The one used by sort is a hack that only works for some of the things that you would want to do.
The one where you use introspection performs badly and isn't typesafe.
The tree implementation in the article cannot be done in a safe and performant way in Go today.
It's not a hack: it's a runtime equivalent that requires that your type implements some interface.
Many smaller projects would benefit too. I would like to build a typesafe tree for an efficient sorted map, and to be able mergsort over multiple trees of different types. It would allow some extremely useful channel combinators for doing rather common things like safely shutting down a service with some background processing. Often these things leak and a often I find myself spawning more go routines just to map between types or to stop generic interface types infecting the rest of the API.
Plus, there are some generic data structures that will really work well in Go, like, for instance, an immutable tree. Granted, it'll still take some care to use properly in Go as it does not have "const" or anything like it, but it can still be done. The problem I have is not with accidental mutation, but that I just don't want to sit there and implement the immutable tree code. (Trees are great, but they're really tedious to write in the best of times, and nightmares to debug in the worst.)
I'm not terribly interested in trying to jam functional programming into Go; I may make light use of map/filter/reduce but even if this was fully implemented it would still be a fairly unpleasant experience (function that return "a value and an error" aren't much fun to map and can't hardly "chain" at all). But I've missed being able to just grab a particular data structure a few times.
How would you re-orthoganize the problem statement? (Statements, actually)
I think idiomatic golang works pretty well without generics in most cases, the big problem for me is that functional programming is essentially impossible without them.
That's definitely not the case from what I know/understand...
Generics enable you to avoid having 10 slightly different implementations of the same thing (even if dependencies are shared, you're still going to have 10 copies of plumbing without generics). Being wise about when and when NOT to use generics is to me the most important factor.
In our shop we tend to not introduce a new generic class/method until something gets painful (unless it's plainly obvious from the beginning). Because of this we usually begin to notice and reason about patterns in our code that seem common enough to warrant refactoring and whether the complexity of a generic implementation would be worth lowering the maintenance burden of copy/pasting said pattern all over the place. It's a case by case decision.
Having generics available at least allows us to make that decision for ourselves.
There must be an answer to this, but I've never seen a good response to why they aren't implementing parametric types and (single-parameter) typeclasses instead.
Simplicity? Yes, but generics are hardly any simpler.
Would you mind elaborating?
I've started a number of large, highly deployed Go projects: Terraform, Vault, Packer, Consul, Nomad, and numerous libraries and other things. I started a company that employs hundreds of full time Go developers. Go has been one of our primary languages since Go 1.0 (and I used it prior to that).
Let me start by saying that there are _definitely_ cases where generics are the right answer. Usually when I talk about generics people tend to assume I disagree with the whole concept of generics but I certainly do not. Generics are useful and solve real problems.
Adding this paragraph after I already wrote the rest: this whole comment ended up sounding super negative. I'm voicing concerns! But, I think that the design proposal is exciting and I am interested to see where it goes. There are definitely places generics would be helpful to us, so please don't take my negativity too strongly.
## Technical Impact
Having written these numerous large, complex systems, I believe there are less than 10 instances where generics would've been super helpful. There are hundreds of more times where it would've been kind of nice but probably didn't justify the complexity of implementation or understanding.
This latter part is what worries me. I've worked in environments that use a language with generics as a primary language. It's very easy to have an N=2 or N=3 case and jump to generics as the right way to abstract that duplication. In reality, the right answer here is probably to just copy and paste the code because the knowledge complexity of using generics (for both producer and consumer) doesn't justify it, in my opinion.
As a technical leader, I'm not sure how to wrestle with this. Its easy today because generics just don't exist so you have to find a way around it. But for a 150+-sized org of Go developers, how do we have guidelines around when to use generics? I don't know yet. I guess that bleeds into human impact so...
## Human Impact!
Something that is AMAZING about Go today is that you can hire a junior developer with no experience with Go nor any job history, have them read a few resources (Tour of Go, Go Spec, Effective Go), and have them committing meaningful changes to a Go project within a week.
I don't say this as a hypothetical, this has happened numerous times in practice at our company. We don't force or push any new hires to do this, but Go is so approachable that it just happens.
I love it! It's so cool to see the satisfaction of a new engineer making a change so quickly. I've been told its been really helpful for self-confidence and feeling like a valuable member of a team quickly.
The Go contract design document is about 1/3rd the word count of the entire Go language spec. It isn't a simple document to understand. I had to re-read a few sections to understand what was going on, and I've used languages with generics in a job-setting and have also been a "professional" Go dev for 9 years.
So what worries me about this is technical merits aside, what impact does this have on learning the language and making an impact on existing codebases quickly?
I really like what Ian said about attempting to put the burden of complexity on the _author_ using generics, and not the _consumer_ calling that function. I think that's an important design goal. I'm interested to see how that works out but I'm a bit pessimistic about it.
---
I have other viewpoints on generics but those are the two primary ones that stand out to me when I think about this proposal going forward.
If you wrote Python, you'd probably be saying, "there are less than 10 instances where type declarations..."; you could make similar statements about concurrency or a host of other features.
And you'd be more or less right.
Here's the deal: a language with generics is very different from a language without generics. You write different code, you solve problems differently, you think differently. This is why they should have had generics in version 1.0 and why adding them later seems so underwhelming to some of us. Generics have a lot of advantages, but you are going to be looking at a bizarre mixture of programming styles for a long time.
They're quite different documents, so I think this is somewhat unfair and misleading. The go spec doesn't spend prose on explaining rationals, alternatives, historical references, etc.
Thus you see very little use of collection pipelining in Go. https://martinfowler.com/articles/collection-pipeline/
Generics are such a powerful and useful idea. Bring it on.
Prior to using Go professionally, I scoffed at the language and wrote it off as an extreme form of Blub paradox. Having worked in languages with generics, as well as languages with advanced type systems (Haskell, Rust, Scala), it seemed like a huge step back.
Initially, I did have a problem with the lack of generics, because I leaned on the feature regularly when writing software. Four years of professional use later and I can say that I am very much glad for the lack of generics. It is almost always straightforward and easy to read and understand Go code that someone else has written. The same cannot be said for the other languages I've mentioned.
For the problem domain we are using it in (devops), it has been a godsend. The company makes use of many different languages from various paradigms, yet anyone can pick up Go quickly if they want/need to contribute to or deeply understand our tooling.
Even with Go being comparitvely simpler, I could go either way on whether it was the right choice to add it to the stack.
I admire golang devs for being opinionated and stand up for the core lines of their language so far.
I see generic as renouncing these principles.
Which shows the absurdity of the situation. The fact that it has generics demonstrates that generics are a useful and important feature. And yet they think their provided generics cover every possible use-case of generics that you will reasonably need in to use in Go.
Also, there's a bit of hilarity about commenting this on a blog.golang.org where the first sentence is "This article is about what it would mean to add generics to Go, and why I think we should do it." being posted by a core Go dev....
func Foo (type T) (t T) (t T, err error) { … }
Seems like an awful lot of parentheses in a single line!It's a bit tricky to read, because 'generic function' here means something subtly different from what 'generic function' means in CLOS …
func (a A) Foo(x int) (n int, err error) { … }
Also your line isn't formatted properly. It should be: func Foo(type T)(t T) (t T, err error) { … } func Reverse (type Element) (s []Element) {
first := 0
last := len(s) - 1
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
}
}
This is later called like: Reverse(int)(s)
Which if this were a curried function, would also be callable like: Reverse(int, s)
Now I wonder if facilities for currying functions might be a higher level addition that could result in the same thing (so long sa we can pass bare types, which is part of this change as well).1. The type should indicate a generic, not a new syntax. This is better because it is easier to parse for the human eye. For example:
func Reverse(first _T, second _T)
2. Since all of what generics are is a compile-time code generator, I suggest that Golang forces developers to explicitly list what types are using the generic: generics(Add) = {int, string, car, food}
func Add(first _T, second _T)
This list can be added to, after importing the package: Import “thing”
generics(thing.Add) += {stuff}
This makes code very explicit about its generic functions. It also allow readers to understand exactly where the function is used.I will admit though that I miss Go's CSP implementation and error-handling convention. Maybe I'll come back to it in the future after they add generics and fix the dependency system.
It also assumes the existence of several megabytes of runtime and enough RAM to feasibly use GC, which if nothing else is probably a real mess when it comes to drivers.
What you probably need, rather than being a second-class citizen of the main Go language indefinitely, is a separate dialect that is close to Go, but can go its own way if it makes sense, like: https://tinygo.org/
Turns out that they’re so useful in practice that they’ve been bolted on to—as far as I can tell—nearly every language in widespread use today. None of these languages “needed” such an improvement. Billions of lines of Java and C# were written without this feature. And yet today it would be virtually unthinkable to release a language without them.
Those people are obsessed, precisely, with the "can be".
Why are we not using C++ where Go is currently being used? By your logic, this shouldn’t be the case.
C++ has a lot more features and therefore it's actually able to be used for a lot more things. People that don't need a language that can be used for every possible computing problem think that maybe C++ is a tad too complicated. Those people certainly have a point.
This is a situation where who is making the proposal is more important than the proposal.
"By Ian Lance Taylor"
Oh. Ok. I guess it's worth reading then...
func (c Connection) Read(type T Writeable)(into *T) (int, error) {
This is quite securely inside shark-jumping territory.Also; I haven't seen Rob Pike's name really anywhere in these blog posts or discussions, or on any recent Go blog entry. Is he still involved with the Go project day-to-day? I always got the impression that he was one of the bigger anti-generics voices on the team internally.
Hence, my modest proposal: Add generics, but make it a brand-new programming language with a totally different name.
Sadly, the name Blub is already taken. I propose we call it Glop. Or perhaps: Gong.