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.
Which of course doesn't change the fact that imperative languages trying to combine generics + inheritance + mutability are in for a world of hurt.
Except for OCaml and Scala (or any other ML supporting subtyping), where you could simply define type's variance.
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.
A super awesome type system is worthless without the basic requirements for scalable software development.
It's not, it's an essential, basic feature.
void lol<A, <B, C<D>>, E>() for (int i = 0; i < 10; i++) {}
to ten random people without prior programming experience and see how many of them can correctly tell you what all that means. Similarly, { a, b in a > b }
is probably not very clear to people who don't write Swift and perfectly lovely closure syntax to people who do.There's language syntax that's actually unreadable, for instance due to using names that obscure or otherwise don't clearly express what a construct is/does or using the same operator/keyword for too many different context-dependent purposes. I don't quite think the sheer presence of angle brackets makes code unreadable, any more than the sheer presence of curly braces or parentheses do.
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.
Visiting https://rust-lang.github.io/async-book/ I don't see any mentions of neither C# nor JS. The key difference here is the audience.
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.
This post is basically a conference talk transcript. More stuff is in: https://go.googlesource.com/proposal/+/4a54a00950b56dd009648... including several callouts to other languages.
It didn't all hit HN, or perhaps more accurately, it probably did all hit HN but didn't all get upvoted because how many times does HN need to chew on it in a month?
With respect to interfaces vs contracts; interfaces are about runtime polymorphism while contracts are about compile time polymorphism. This is an important difference when you consider []interface{Foo()} vs Slice(contract{Foo()})--an instance of the former can contain elements of varying concrete types while an instance of the latter can only contain elements of the same type. The other important detail is that interfaces only abstract over a single type while contracts support multiple type parameters (a single contract could specify a visitor type and a vistee type, for example).
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.
Or constructors combined with static objects; you get the static initialization order fiasco.
Or constness with template functions; you get two, four, or eight copies of each generic function in your source code according to which things are const.
Or (compile-time) overloading with (run-time) overriding; you get C++’s weird “hiding” rule about the other overrides you didn't override.
Or separate compilation with implicitly instantiated templates; you get geological build times as the compiler instantiates the same templates in every .C file and then throws away all but one of the identical instantiations at link time. (To be fair, this is far from the only reason C++ compiles slowly.)
Overriding combined with type conversions through implicitly invoked constructors (and implicit referencing and implicit casting to const) gives you annoying bugs that are unnecessarily hard to figure out.
Cleanup from exceptions via RAII combined with C’s unspecified argument evaluation order led to a situation where resource leaks during certain kinds of operations couldn't be avoided reliably, a bug in the language definition that wasn't noticed for several years, though I think it's fixed now.
The grammar is undecidable because of the number of different things that have been added, which sounds like a hyperbolic joke but is actually literally true, and a significant obstacle to implementing something like gofmt for C++.
The combination of template parameters using <> for parameters, the traditional longest-leftmost tokenization rule, and the >> operator for bitshifting made foo<bar<baz>> an unexpected syntactic pitfall, one that is now fixed.
There wasn't an exception-safe version of the STL for a number of years, which isn't really an interaction between templates and exceptions —a non-template container had to deal with the same exception-safety problems— but it did mean that fit quite a while you could use the STL or exceptions but not both.
This is far from the extent of the problem. The C++ FAQ consists largely of affirmations of the form, “Doing [reasonable thing 1] works, and [reasonable thing 2] works too, but if you do them both, you will die horribly for your immorality.” It's exaggerating about the death part but the surprising problems are quite real.
I don't hate C++, and I think it's the best existing language for some problem spaces, but it's very much the poster boy for unexpected problems arising from interactions between features.
As far as the keyboard goes, https://GitHub.com/kragen/xcompose and http://canonical.org/~kragen/setting-up-keyboard are your friends.
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.
Note that, for all of that, I'm still in favor of adding generics in Go, as I do see the value they can add. I'm just pointing out that "bashing" on Go devs on not knowing what generics are or how they can be useful is both non-productive and probably generally wrong. Plenty of us are saying "Man, if I had generics, this would have been less code/cleaner/more reusable!", just the other various tradeoffs outweigh the actual cost of using a different language. Go's ability to drop a very junior dev (or just one that has never use Go) into a codebase and have them be almost immediately productive is incredible.
(I also think it makes sense for TypeScript given JavaScript idioms; it mainly falls over when the caller has to interpret some very complicated error messages, however)
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"...
a := Bar(baz)(buzz)
Is Bar a `func(some) func(thing) other` or is it a `func(type T)(some) other`? You need to know what “baz” is to be able to tell.Is that a function that returns a function or a generic Reverse function specialized over type x, invoked with argument y?
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.
Find smallest/largest element in slice
Find average/standard deviation of slice
Compute union/intersection of maps
Find shortest path in node/edge graph
Apply transformation function to slice/map, returning new slice/map
And here are data structures that other languages have but go does not: Sets
Self-balancing trees, with efficient insertion and traversal in sorted order
Multimaps, with multiple instances of a key
Concurrent hash maps, supporting parallel insertions and lookups with no single lock
If it were not a hack, then go would not have these limitations.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.
How do you manage to get a job as a developer without having ever learned this stuff?
Are these people that are just self taught straight to javascript / ruby?
I mean, are CS course nowadays so bad that students come out of them without understanding things like type-theory, pointers etc
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.
When it came out, it was possible to C&P pictures to emails and other fancy stuff that wasn't possible before.
EDIT: If any Apple employees are reading this, I really wish autocorrect would stop confusing generics and generics. :)
I'll be really cool to see a language that feels consistent, tight, well-engineered, and small; can create powerful abstractions similar to what can be achieved using CL macros and CLOS; and has an awesome debugger, editor interop, interactive programming with a REPL, build tools, etc.
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.
Its lack of impact is why I dont really care about the subject. Adding or removing generics to a project matters little. So why bother complaining. The class creation and encapsulation choices are much more impactful to future code edits.
Go already has generic built-in maps, channels and slices, so clearly those generic types are useful.
If what I actually want is a multiset, say, it would be great to have a standard library class that looks and behaves similarly to map except it’s a multiset, rather than just having to use map and do all the little 1- and 2-line boilerplate tricks for the multiset operations. I can write shorter code that more clearly expresses my intentions, and reduce the risk of stupid little bugs in the boilerplate.
If you’re thinking of functional stream operators, many people think plain imperative iteration is easier to write (and easier to read).
Yep. I held this opinion before Go came out, only because I couldn't write an ORM in Go.
You basically just post hoc rationalized their indifference for ORMs by making them sound incompetent
As if there are no other arguments for avoiding them that perhaps seem valuable to people that are not you?
It sounds reasonable to me you conjured the hot take, acknowledged it was post hoc rationalization on your part, but posted anyway, projecting your capacity for after the fact rationalizing onto ORM haters in the Go community.
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.
That said; C++ is a ridiculously complicated language. I don't get why people think that Rust is difficult.
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.