Generics enabled by default in Go tip
go-review.googlesource.com
go-review.googlesource.com
On the other side, there are cases, even if they are not very frequent, where generics do help a lot. Just the new slices package allone almost justifies the addition of generics. Furthermore, the proposal looks very much Go-like. As a consequence, I am quite looking forward to try them out and used responsibly[1], they can be a great addition to the Go universe.
1: I think the situation is somewhat similar to Lisp macros. Normally, a codebase rarely requires macros and readability is better, if you avoid creating too many macros. But occasionally there are situations, where using a macro tremendeously increases code quality. Both implementation and readability-wise. There macros should be used and it is great to have them available.
Go is one of the hardest languages to learn. First of all, some concepts in it are very different from "mainstream" languages, and it takes a while to get used to them.
Simple things that exist in almost every other ecosystem can be absent in Go. Want to run setup code before your test suite? You on your own, buddy. And, oh yeah, 2/3 of your code will be `if err` statements, because exceptions are passé.
I learned it by seeking out good Go codebases, which are incredibly rare. Hashi's Terraform comes to mind.
Infamously, the early Kubernetes code was a nightmare because it lifted and shifted Java idioms and that was at the company that invented the Language.
Or at Uber, where they passed on Go's native goroutines and channels in favor of homegrown concurrency, for SOME reason: https://eng.uber.com/go-geofence-highest-query-per-second-se...
All this to show that Go is not a simple language to do well, so let's dispense with that myth.
It has its uses, but having done it, I would never recommend Go for a greenfield project in my place of work - there are very few rules that come with it, and it requires tons of coordination and discipline among a team. And if you have to work with 2 or 3 other people who have strong opinions on how to do things in Go, watch out.
But you can get a pretty good basic Go intro in a few days and be pretty productive after that. It isn't that different from most languages and has a rather small core. Of course, it helps if you have Scheme experience, to understand all the power of high order functions etc.
The first is a purely theoretical exercise, the second a applied one that involves not only the language but also its ecosystem (implementation(s), library of functions and tooling)
I think we mostly agree, too.
However, I distinguish between learning a programming language and learning to use it. I learn many programming languages without ever writing any line of code in them.
Once you’ve seen enough languages, if you combine that with reading about other people’s experience with working with the language, that can give you a decent idea about how the latter is without having to spend the effort (without being as good as doing it yourself, of course. Apart from “which one is the brake pedal”, I think I know how to drive a car, but I also know I can’t drive a car)
That’s why I posted my first comment: the poster you replied to may share my opinion between “learning” and “learning to use”.
Wow, interesting. I'm in Japan.
BTW, the readability is really not good as Go 1.
Option types are pointless if you don't have exhaustive pattern matching to ensure correctness.
That would rather miss the point as you'd be converting the type-safe result to an unsafe version?
Go doesn't have pattern matching or any "structural" construct which would allow performing this in a single step, so the alternative would be to use HoFs and while Go has them they're quite verbose e.g.
func (o Option[T]) Then(then func(T)) {
if o.OK() {
then(*o.value)
}
}
a.Then(func (v int) { fmt.Printf("\tvalue = %d\n", v) })
which is… less than great.An safe other option would be a method mapping to a slice of 0..1 element and you'd use a `for range` as an `if` but that seems way to clever, similar to Scala's comprehensions over everything which I rather dislike.
Also sadly the current proposal has no facilities for methods generic in their arguments independently from the subject, so no `Map` method.
func (o Option[T]) Unwrap() T
How does Go know that T is a type parameter here, and not a concrete type named T?> Generic types can have methods. The receiver type of a method must declare the same number of type parameters as are declared in the receiver type's definition. They are declared without any constraint.
// Push adds a value to the end of a vector.
func (v *Vector[T]) Push(x T) { *v = append(*v, x) }
> The type parameters listed in a method declaration need not have the same names as the type parameters in the type declaration. In particular, if they are not used by the method, they can be _.That methods can't be parametric is still annoying though because it means utility methods can't have generic parameters unrelated to the subject e.g. can't define a map or fold method, because there's nowhere to put the output type, it has to be a free function instead.
But it's not like adding the generic spec thing between the subject and the method name would be a big issue so that can always be added later after the MVP has been exercised a bit.
A related "missing bit" which I'd guess they want more feedback before adding would be "conditional methods" aka methods which are only present if the generic types of the subject have a specific property (e.g. in Rust it's common for the generic container to be unbounded but for functions to be bounded on specific traits).
It's very unfortunate thay didn't do this from the start. The original paper that introduced Go generics had this and used to to solve the Expression Problem.
As for generics, Go’s lack of function overloading and arg default values is really interesting. It ensures that a function is always easily found as it’s the only thing in the module with that name. I’ll be curious to see if generics are easier to follow without function overloading. It will still be only in one place, where as in C++ you could have hundreds of functions with the same name across many libraries and you just have to hope your ide knows which to step into.
There are already a number of langages with generics and without overloading (or defaults). Haskell, ocaml, rust, …
That's no more overloading than Rust has through traits.
In Rust, unlike in Haskell or ML type inference stops at function boundary.
Very skeptical, but the go devs have given me plenty of pleasant surprises before, maybe we get another one here.
I did a quick look through all my open source projects to see where I have accepted or returned an interface{} in my own APIs, which is a strong signal that I'm looking for generics, or am using a library that wants generics.
One case is interacting with Kubernetes. I have two applications where I set up a reflector like `cache.NewReflector(lw, &v1.Node{}, store, resync)`, to keep a cache up to date; the implementation of "store" takes interface{} for all the arguments (Add, Delete, etc.), even though it only ever operates on a v1.Node. Generics will let me ensure at compile time that I only have to worry about v1.Node objects. Right now, that's handled with runtime assertions in the reflector itself, and in every implementation of the cache.Store. Generics clean this up.
Another case that comes up is processing random JSON objects from the Internet with no defined schema. Those end up as map[string]interface{}, and that won't change.
The last case is unmarshaling functions. I see some (in a JWT validation library) that are of the form `func Unmarshal(string) interface{}`. I don't really know what's going on there, and I don't know if generics will help. (Similarly, for the common case of unmarshaling JSON, I'm not sure generics can improve the `err := json.Unmarshal(bytes, &result)` API. I will have to look into it.
That's about it.
Imo Golang buried that theory. I’ve read a lot of Golang code and it’s always been the clearest and most readable code I’ve read.
This has the general effect of making every piece of code familiar, because every piece of code is forced to use the same ideas to solve adjacent problems.
The foil to this is javascript, where you've got a bunch of different ways to set up functions and promises and async and threading and communications and error handling, etc. Javascript is a very flexible and high level language, but that also means everyone solves problems in their own unique way.
So… you know wrong?
Templates are a form of generics. What templates are not is an instance of parametric polymorphism.
The culture might help. I think generics in Java are not overused -- partly because they are not so powerful and partly because Java, similar to Go, did not have generics for a long time.
Powerful, though.
As someone who’s working in a Go codebase at work, I’m happy they added generics, especially in the way they did. It’s a pretty minimal subset of generic functionality. I will apply it in certain cases, mostly to reusable code. I think it’s a big win overall though, without introducing too much complexity to the language.
Golang generics proposal has been accepted - https://news.ycombinator.com/item?id=26093778 - Feb 2021 (168 comments)
Go generics proposal moves to “likely accept” - https://news.ycombinator.com/item?id=26018649 - Feb 2021 (92 comments)
A Proposal for Adding Generics to Go - https://news.ycombinator.com/item?id=25750582 - Jan 2021 (270 comments)
The Next Step for Generics - https://news.ycombinator.com/item?id=23543131 - June 2020 (664 comments)
Generics in Go with Ian Lance Taylor (2019) - https://news.ycombinator.com/item?id=22361089 - Feb 2020 (98 comments)
Why Generics? - https://news.ycombinator.com/item?id=20576845 - July 2019 (254 comments)
Go's updated generic proposal (Contracts) - https://news.ycombinator.com/item?id=20541079 - July 2019 (61 comments)
https://go.googlesource.com/proposal/+/refs/heads/master/des...
1. what's "Go tip"?
2. does this allow us to approximate when it could make it to one of Go's stable releases? I guesstimate it's something that would land not sooner than within a year, right?
The plan is to have them available as a preview feature in the next stable release, which is scheduled in about 6 months time.
Why have we all so strongly coupled our identities as programmers to the language we use? Sense of community and a perceived need to defend it?
I don't think Go needs generics, but I'm not about to invent obscure edge cases to justify for/against the idea. That's a recurring theme in all defenses of any language. It's not helpful.
Use Go if you like the "clarity", stay away from it if you don't like the "verbosity".
These are both valid points, but they're subjective. Pushing it as fact is dishonest and self-serving, as well as eventually detrimental.
Because the viability of a language is linked directly to its ecosystem and big names using it, so people that want to use one language have to promote its use if it's not in the top 5. You don't see many people advocating for Java, but some people are advocating for C#, Kotlin, Scala. You don't see many people advoating for JS, but many people are advocating for Elm, TypeScript, ReasonML.
On the other hand, people that are "forced" to use a language want to bring features from other languages they'd rather use. You can see in this graph https://go.dev/blog/survey2020/missing_features.svg (from https://go.dev/blog/survey2020-results) that people that want Go to change want to bring features from ML, Java, Rust, etc. Other languages. Because they have to work with Go or want to work with it but want to bring what they already know with them.
Lots of people spend 8 hours a day using the programming language that their company chose. Adding a few things that they like to it seem like a good way to make their life better.
To be honest, I'd use Ada if it had a significant infrastructure, but it feels pretty much dead. With Go I can at least get 3rd party libraries, as long as I check the source code to see if it's good enough.
Anyway, interesting to see how far they will take the generics in the path to Go 2.
Borland BIDS 1.0 used such approach with pre-processor tricks, similar to many C projects do as well.
Java, well EMF was a common tool to generate such boilerplate, Velocity was another one.
I've also learned that in most cases you don't need to invent some giant application. It's far easier to create extensions or wrappers to existing operations or service tools, with a bonus (again) being easier hiring and delegation. You can hire someone who knows a standard piece of tech and quickly ramp them up on a small extension or wrapper that you wrote. Golang is the language that made me always take this simple approach first and look for something that does 80% of what I need and think about problems very generically.
Creating a bunch of microservices and connecting them has just moved your complexity from the "monolithic app" kind to the "integration" kind. It's much harder to understand the potential impact of your changes when "anything" could be using a service via the network.
It's a trade off here. If you have integration complexity, you need integration heroes.
> I've also learned that in most cases you don't need to invent some giant application. It's far easier to create extensions or wrappers to existing operations or service tools, with a bonus (again) being easier hiring and delegation. You can hire someone who knows a standard piece of tech and quickly ramp them up on a small extension or wrapper that you wrote. Golang is the language that made me always take this simple approach first and look for something that does 80% of what I need and think about problems very generically.
This is a great idea. Leverage industry experience when and where you can. Minimize the amount of "tribal knowledge" that your team requires to be effective.
(edit) A parody about those: https://www.youtube.com/watch?v=y8OnoxKotPQ
> I try to extend the simplicity of golang all the way to the network architecture.
The force part comes from a desire for business continuity and efficiency. If you're a large scale Go shop there's a fair amount of specialized infrastructure needed including a goproxy to prevent leakage, some apparatus to view documentation, and probably educational resources.
A large scale Java shop needs extensive artifact storage and a place to host generated Java docs.
If you employ both languages your infra cost is now going up for something a business sees as pure overhead rather than the conduit for innovation. In my experience this leads to language supremacy discussions, bickering over scarce funding, etc.
On my team, we're polyglot. Everything, including docs, are generated and self-hosted internally. We make the best of a brisk situation. What lets me sleep okay at night is that developers have a choice to use the best tool (language) for the job, not just the one that a company hired for ten years ago.
I understood the philosophy of Go and why it didn't have generics or may be I'm just convincing myself of that because I'm invested in it. I think when you accept the philosophy of something you already get coupled into it.
I don't really have an opinion about Go bringing in generics, I would use every time saving feature available in a programming language. On the other hand I really hope additions like these doesn't become a point of friction among those who really have an opinion, Especially those who have been contributing to Go and end up forking Go.
[0]: https://dave.cheney.net/2018/05/29/how-the-go-runtime-implem...
They're not generic (aka userland) generics, but they're still generics: parametric types, and functions able to work on them generically (you don't have a separate `append` function for every type you might put in a slice).
> The generics proposal is being implemented from the ground up.
Of course it is, the builtins are ad-hoc and half-assed. That doesn't mean they ain't a thing.
In what way are slices and maps half-assed?
I really don’t think they were saying that slices and maps themselves are bad. They function perfectly. I’m sure they meant that it’s just silly to confine quasi-generics to those 2 places.
Why is it silly?
We’ve made societal progress on a lot of things, but not so much on this one.
For me, I always try and be conscious of that, and I tell coworkers when something that I’m suggesting is just preference over objective fact. For example, I prefer Ruby as a scripting language. My first job used it for various automations, and I got used to it. It has absolutely no value over Python as a scripting language for example. It’s just my preference.
But even without having used it I always thought the lack of generics was very interesting and I could see how it was desirable. It's fascinating to me that Go has gotten as far as it has without them (proving that it's possible to), and the mindset shift people describe having around them seems like a really important thing to pay attention to.
One thing I'm curious about: user-defined generics I can see going without, but the core language and standard library surely need to have them. How does that work in practice? Is there a syntax for using them and just not one for creating them? How are they defined in standard library code?
They are defined in the builtin pseudo-package: https://pkg.go.dev/builtin
They’re special-cased in the compiler, with bespoke implementations.
Yes, the mindset of the people was certainly a key part of this, but another key part is that Go has interfaces, which are existential types, which are dual to universal types (colloquially called generics). If the type system is expressive enough (System Fω), you can express one in terms of the other. Of course, Go's type system wasn't expressive enough to realize the true duality, but it was expressive enough to express many things people would have used generics for in other languages.
Other languages which don't have generics don't have existential types either, and people see Go through that lens and don't understand how we got anything done. Well put on some existential glasses.
As an aside, one of the reason it was so hard to develop a good design for generics is that they inevitably interact with interfaces, precisely because of the duality above. Unfortunately, the non-language experts who complained on web forums about the lack of generics in Go for the past 11 years do not understand what that means, but that didn't stop them from complaining. Let me put it another way. There's a reason why Rust traits are not types, but Go interfaces are types.
The biggest reason was probably that it had bespoke generics for a bunch of core collections, significantly mitigating the need for userland generics.
Without the builtin generic slice, map, and channel, the issue would have been significantly less tenable on both efficiency and convenience fronts.
It's not like "we have interfaces and we still need generics" is a new thing.
The value of generics is quantifications over types. In particular univeral quantification. "Generic" slices don't give you that, because the values are "generic", but there are no non-static type constructors. Go slices are "generic" only in the same sense than arrays in C are generic, or (if we go to the degenerate case) that a variable is "generic" because you can declare it with any type. I'm sorry, what?
Go has existential quantification, something which C lacks. Go is very unusual that it started with existential types. Languages based on System Fω have universal quantification as a native operation and implement existential types as higher rank universals. The fact that Go had existentials from the beggining is not some technical nitpick, it is what gives it type-level expressive power over languages like C. Ignoring this in arguments about generics is missing the point completely.
go run -gcflags=-G=3 foo.goThat said, I think some of them may have a point that Go shouldn't actually try to tack on generics now. Consider that generics are only one piece of the puzzle when it comes to programming language power and abstraction capability. There are also ADTs, abstract types, pattern matching, immutability, lack of null, expression-oriented syntax, etc.
Go with generics would be only one step along that journey of abstraction power. It would help the set of people who desperately need it to solve their code repetition and type-casting issues. But it would solve these problems just to clear the way for them to reach the next set of problems on the journey of abstraction.
To the people moaning about how generics will make their favourite language as awful and ugly as Java: all of the libraries and techniques you like and use today will always work. Little things like sorting will use generics pretty much transparently. All of the strongly typed code you write today that receives and returns concrete types will be just as valid tomorrow as it is today. Interfaces keep the same semantics and are a core part of how generics work.
I think it’s important to remember that the people who design this language like it for the same reasons you do.
In fairness: just because that's true doesn't mean the libraries they like or need won't be migrating to generic facilities one way or an other, so they may be forced to the choice of interacting with generics or needing to reimplement their wheels (though also in fairness that's also common in the go ecosystem).
> I think it’s important to remember that the people who design this language like it for the same reasons you do.
That's not necessarily true, the designers of the language are not necessarily designing it for their use or like. See: Rob Pike's well known quotes on the target population for Go.
Welcome to 1976.
* should the builtin generics syntax be compatible with the new custom syntax
* how many problems will be solved by the custom generics and how much complexities will be added. Is it a good balance?
I feel like it's a change to placate many, while driving a lesser amount away. Which is fine, but still feels like the end of something, as I am one of the aforementioned 'lesser.'
As for why...I love above most the simplicity and readability of Go. Any change which encroaches that, which this does, is a net negative to me.
I was very negative towards generics and had similar arguments as you. It didn't take long before I changed my mind. I'd never want to write Java without generics again.
I'm not saying you're going to have the same experience, but I would suggest you give it a try before dismissing its value.
When most users people ask about type erasure, they ask whether types are erased when compiling the reflection information for polymorphic values (like Java does). Go doesn't have polymorphic values, so the question doesn't apply.
Type erasure means something more general than that to compiler people. Type erasure refers to whether you can compile code while erasing its type, and it's generally a useful property. In general statically-typed programming languages are implemented with type erasure, while dynamically-typed languages are not. Of course, since Go has reflection, it can't do type erasure. Interfaces also conflict with type erasure somewhat, though as expected, during the code generation types are erased as much as possible.
As a comparison, Rust does type erasure fully.
According to the spec:
> It's impossible for non-generic code to refer to generic code without instantiating it, so there is no reflection information for uninstantiated generic types or functions.
You won't be able to reflect a generic List or List[T] at all which… makes sense, I think? `reflect` works on values at runtime, so it necessarily works with instantiated types as you can't have a value of an un-instantiated (generic) type: what would you give to `reflect.TypeOf` which could return an uninstantiated generic type?
I found a few places where refactoring had left behind a wrong type cast. Production logs demonstrated actual crashes corresponding to these cast. There were bugs in the issue tracker, marked unresolvable.
Yes there will be some pockets of libraries where authors take generics to an extreme. Besides being a bit of fun, it's good to explore the edges of what you can do with the language, so this exercise will be valuable even if you don't use them regularly in your projects.
But I don't really see pervasive gratuitous use of generics becoming a big problem for the average lib, Go devs tend to lean practical on potentially abused features like this, and there will be many users like yourself that will still prefer simpler interfaces. I guess we'll see over time.
Really, Go's lack of generics for so long wasn't an ethos (despite many taking it that way) but more that the right implementation just hadn't revealed itself yet. I think the one they chose is a pretty good match for Go, and I hope that it makes Go more complete.
That being said for a lot of other uses, you really do want high quality data structures beyond "array" and "dictionary."
Also higher-order functions are a moot point. Higher-order functions give you convenience, but no increase in expressive power (defunctionalization is an homomorphism).
Of course, generics give you a true increase in power.
All our microservices return a { result: ... } or a { result: ..., nextPageToken }
We would definitely benefit from generic SingleResult<T> and PagedResult<T>.
Instead, you copy-paste the same definitions over, and over, and over, and over again for every call.
type NextPager interface {
NextPage() PageSpec
}
func (c *Client) FetchNextPage(ctx context.Context, current NextPager) (interface{}, error) {
...
}
Then for each type of paged object, you write: func (c *FooClient) FetchNextFoo(ctx context.Context, current Foo) (Foo, error) {
next, err := c.client.FetchNextPage(ctx, current)
...
if n, ok := next.(Foo); ok {
return n, nil
}
return Foo{}, fmt.Errorf("unexpected type: got %T, want Foo", next)
}
That's annoying. But, this problem has come up before with `sql`, which has rows.Next() and rows.Scan() to iterate over arbitrary row types, and you could use that as a model: pages := client.Query(...)
defer pages.Close()
for pages.Next() {
var foo Foo
if err := pages.Scan(&foo); err != nil { ... }
// do something with the page
}
Generics would let you enforce the type of `foo` at compile time, but it wouldn't save you many lines of code. I think you still have to write (or generate) a function like `func (c *Client) ListFoos(ctx context.Context, req ListFooRequest) (Paged[Foo], error) { ... }`. We hand-wave over that in the above example with a "..." passed to query (potentially possible if you retrive objects with a stringified query, like SQL or GraphQL), but that sounds like the hard and tedious part.Let me conclude with a recommendation for gRPC and gRPC-gateway as a bridge to clients that don't want to speak gRPC. Then you can just return a "stream Foo", and the hard work is done for you. You call stream.Next() and get a Foo object ;)
You literally showed that "for each type of paged object, you write <multiple lines of entirely unnecessary code>".
Where with generics you just have a single generic function.
> We hand-wave over that in the above example with
The problem is: there's no hand-waving in reality.
The verbose patterns (if err!= nil for example) make the code predictable to read, you notice the code smell of missing error handling really fast.
For example here's a code in Go to look for a Prime:
func IsPrime(n int) bool {
if n < 0 {
n = -n
}
switch {
case n < 2:
return false
default:
for i := 2; i < n; i++ {
if n%i == 0 {
return false
}
}
}
return true
}
It's readable as it is simple to understand what each line does.Here for example is a code that does the same thing in Rust:
fn is_prime(n: u64) -> bool {
match n {
0...1 => false,
_ => !(2..n).any(|d| n % d == 0),
}
}
It's might seem more complex at first (what does match do, what 0...1 means, !(2..n) what is any() doing. But if you understand the language it actually this seem much simpler and you can quickly look at it and know exactly what it is doing. And because it is less verbose it is easier to grasp the bigger code.I also noticed that while individual functions in Go are simple to understand and follow, you can still create complex, hard to follow and understand programs in Go.
static boolean isPrime(int n) {
return switch (n) {
case 0, 1 -> false,
default -> !IntStream.range(2, n).anyMatch(i -> n % i == 0)
}
}That seems unfair when comparing:
Removing negatives from the Go implementation removes 6 out of 16 lines, bring it from 3x Rust to 2x Rust in line length.
The Rust code needs maintenance coders of way higher caliber, not something you'd usually find. It's super fun for the top-tier developers who love to be expressive and concise with their code, but all code is pushed down to maintenance mode eventually when the hotshots move on to the new shiny project.
Go has removed pretty much every footgun by sticking to the basics. You have one way to do a loop, one way to do comparisons etc. There are very few ways to hide non-obvious functionality.
It _is_ possible to create complex programs, that are hard to follow but that's a larger design problem. Not something the language can force on developers.
Don't get me wrong, I'd take Rust generics over codegen 8 times out of 10. But that ORM worked well, and was the only time I ever needed to write a code generator in Go.
If they didn't, they would just not create another solution.
Having said that you might be right with ORM though. One of great reasons why ORM might be superior on statically typed language is that you can rely on the type system to ensure you writing correct code (you also get benefit of autocompletion, refactoring in IDE etc). The problem though is that the type system including generics might not be sufficient to express it. So code generation could be still superior here.
The JOOQ (not exactly ORM though) generates java code, even though Java has generics.
BTW: I personally think though that actual proper way to handle this problem is to what JetBrains did. They integrated DataGrip into their IDEs (I think it's available in the paid version though) after you connect IDE to the database, it starts detecting SQL statements in the code and treat it the same as rest of the code (i.e. auto completion, some refactoring (they still need to improve that more) etc). It makes an ORM no longer necessary for me. I think that's probably the way to solve the impedance problem.
As someone who hasn't used Go, what would I do, then? The question about a generic data structure was very practical, your response was philosophical, and I still need a linked list.
It isn't as if we never coded without generics before.
If you really need generic structures you can use the interface type but generally there's a simpler solution that doesn't require generics.
Having said that, I honestly don’t know if generics are a net positive or not. What I can say is that go is one of the few languages where I can jump into an arbitrary go code base and make sense of it relatively easily. Risking that is a scary proposition for me.
> Having said that, I honestly don’t know if generics are a net positive or not. What I can say is that go is one of the few languages where I can jump into an arbitrary go code base and make sense of it relatively easily. Risking that is a scary proposition for me.
I don't see how type params would make this harder, I daresay I can think of a lot of instances it's much easier. The caveat is that you simply don't understand them, in which case it's a good thing to learn as many languages do have them.
In any case all these arguments are well trodden so I'm probably wasting my breath re-hashing here.
Generic data structures are provided in many programming languages and there are many people that are very experienced in writing some of these, so being able to reuse, it's precious.
In general the lack of generic price surfaces when you write libraries, not applicative code. But libraries are a big portion of a codebase.
That's pretty laughable considering the language designers included type-parameterized collections in the language. Apparently they recognized the need for them; they just didn't think you were smart enough to make your own. After all, Go was explicitly designed for programmers who are, in the words of its creator, "not capable of understanding a brilliant language".
Sometimes - especially in other languages - you see a piece of "elegant" code that does something complex in line 3 lines, and you think "hmm, this is a puzzle, and I'm going to be staring at it for 20 minutes before I'm convinced that its 100% correct."
We actively tell our engineers not to be clever. Write boring code that is obviously correct, and don't worry if your boring code is 25 lines when the elegant code is 8. It takes you longer to write the elegant code, and it takes the reviewer longer to read it, and often times (though not always ) it's also more difficult to test.
I love Go because of how boring and consistent and easy to read it is. The language and the task of "programming" melt away and instead you get to focus on solving problems.
Using generics isn't 'clever'. It's like saying a loop is clever. It's abstraction, the opposite of clever.
Even for your own single-person projects - if you get fancy with the code, 6 months later you find it's a lot harder to get back into and mess around with than if you had written the code as though you were presenting it to a beginner.
I don’t agree with you, but I could be wrong. There are plenty of languages that are aligned with your point of view. I like go because it was going a different direction, and I hope that doesn’t change. You can use Haskell, scala, typescript, etc to get what you are looking for.
But some people have a tendency to play code golf with their codebases.
I have, for example, encountered a "generic data structure" that looked like a normal linked list on the surface. BUT, it actually sorted the largest three items in the first 3 cells and the average in the 4th.
That was multiple days of work wasted because someone decided to be cute with their data structures. And that wasn't even the only one of such "generic" monstrosities in the code.
It's true that applicative code shouldn't need generics in probably more than 90% of the times, however the lack of it affects library authors quite heavily
In Go prior to generics, interface{} is an escape hatch less frequently needed but not necessarily much safer than void*. Post generics, interface{} is suddenly more useful and will be aliased by ‘any’.
The way the std lib heap works in Go, an implementation doesn’t have to mention interface{}. Using the Go std lib solution is about satisfying a few interfaces, defining some methods for sorting and swapping over the element type. The use of interface{} is internal.
Go’s generics solution is going to have type constraints, which I think will be very familiar to some and probably new to others … So, the Go generics PQ should still require some constraints on elements, not ’any’thing will work. I’ve really enjoyed constraints in languages and it’s not quite natural in C++/Java, but Go’s interfaces already do some ‘constraint’ work conceptually and can be used as constraints in Go’s generics syntax. I’m interested to see how this plays out.
Doesn't mean many of us want to keep living on that world.
I advise reading books like "From Mathematics to Generic Programming"
In my 20+ years of development, I've definitely realized everyone's brains and approaches work differently.
I've spent time in C, Python, Go, Js, and some lesser known languages. I was really big into Python. Go seemed restrictive at first, but was immensely more safe and predictable.
Linked lists serve a real purpose in some situations where you really do want O(1) behavior. One example is when you are performing the operation while holding a mutex. But it's never the sort of thing where the right tool is a linked list generic container.
That is what those CS algorithm and data structures lectures are for.
Generics are adding a tool to the toolbox. They can be used well or they can be mis-used. I recently wrote a gui app in Golang using Fyne and I really wish I'd had generics for some of the UI handling - instead I ended up having to write some really ugly and not simple code to handle the case. The end result was worse and less-simple than if I had been able to use generics.
https://news.ycombinator.com/item?id=28254411
The "solution" to this is copy-pasted boilerplate code with casting for every call.
Hello-world level projects with 20 class deep hierarchies of generic classes, just to make it "generic" in case you need it later.
Some people will abuse generics, no doubt, just like my colleague abused packages. But Go has such a strong culture around idiomatic code that I’m not too worried that generics are the end of the world. In fact, I don’t like keywords like “append” and I’m hopeful generics will replace them.
TBH I was more upset about the Go 1.17 ability to panic during a type conversion from slice to array pointer. That one really grates my gears.
What are you talking about? There is already a while loop, it's just under a different name. I don't think reusing keywords for other purposes is an example of simplicity.
Just look at Perl. Stuff written 20 years ago is likely to work without change on the newest release more often than not. For certain work contexts, like system utilities and long life core programs, this can be amazingly useful.
And all it will cost you is the slow decay of your community until eventually they're almost all gone, and nobody releases supported libraries for you anymore, and there's not even enough community left to provide community support for many new services and technologies.
If you stick around one thing long enough you're destined to be disappointed one way or the other.
While FOSS might have changed the way to sell those products, they are still products looking for attention, market share, ecosystems, conference talks, consultancy, trainings.....
And even then it's latest language standard update was in 2018. You still have to deal with the fact it's changing and keep up with it if you plan to read or use others code. And of you don't, then you can safely not care about advances in C, just as you can safely not care about changes in Go.
Although in typical C fashion is a bit of kludge.
And for the record, I'm mostly in favor of adding generics to Go.
[0]: https://talks.golang.org/2015/simplicity-is-complicated.slid...
I do need and want generics. So... Whose needs and wants are more important?
Why? Why so negative?
I work in C++. Generics exist there. I don't use them except for some STL containers. I just... don't use them. I don't have problems where I need them. How does it hurt me if they exist?
You may say that someone else will use them, and make your code more unreadable. They may, but... if they really help the code base, use them. If they don't and someone uses them anyway, teach them some taste and discernment. If they can't learn, then you've got worse problems than a language that has generics.
If it's borderline, but against your personal taste, then yeah, you're kind of out of luck. On the other hand, personal taste changes over time, often from experiencing new things. You could try it for a while, and see how it goes...
But I know what happens in real codebases. Before long, scammy tutorials pop up showing Go as an essentially dynamic language, and that's what bootcampers write. As of today, they are forced to write simple, boring code.
I never mind a carefully added thing, for carefully thought of situations, as this probably was.
The problem is that I see the abuse from here. And even if not abuse, it changes how you approach problems. And approaches matter as much or more then spec.
I knew a person who insisted on a lot of things when working in a Go code base...one of which was mixed typed tuples. It was ugly, awful code of interfaces all the way down. It's ugly and Go let you know that. A voice of reason would say...what are you doing? That's not how you do it in Go.
Readability goes beyond the syntax. It's a mindset. And now it's not.
Sorry are you saying adding generics to Go makes Go closer to dynamic languages? Shouldn't that be the opposite?
> I knew a person who insisted on a lot of things when working in a Go code base...one of which was mixed typed tuples. It was ugly, awful code of interfaces all the way down. It's ugly and Go let you know that. A voice of reason would say...what are you doing? That's not how you do it in Go.
Do you have an example of this (code)? What's wrong with tuples with elements of different types? What's the alternative?
Can I write a generic data structure? No. You have to cast from interface{}.
Java 1.4.2 is dead, and rightly so.
And manual error checking? No. Get that garbage out of my control flow. I'm not going to a language that has worse error handling than C. At least in C you can factor it out.
And like i said generics have a place its just a way smaller place than many people think
I like to liken it to house construction. Imagine if a number of people demanded that natural gas and water be combined. Why do I have to make two separate pipes? They even look the same!
It's about type safety - i.e. catching bugs earlier and more reliably. Every time you have to write an explicit downcast, you are throwing away the benefits of using a statically typed language in the first place. Which is fine, but then why stop halfway, and not just go full dynamic? It doesn't really make sense to have strong typing for scalars but not for collections.
Abstractions in libraries? Ok maybe.
In the higher level code that the vast majority of programmers actually write? No. Thank. You.
Besides, interfaces cover the majority of generic problems on the ground.
I know that doesn't help you when you have to interact with the larger Go ecosystem on Github, but at least within an org, it shouldn't be terrible. Certainly the culture of golang is well established— millions of lines of it have been written generic-free, so I'd expect that it will only be applied where it really is an obvious improvement.
Go adds generics.
HN thread: I don't want this.
Good case study about the people drawn to comment on a topic.
When go didn't have generics, those in favor commented. Now that it will, those against are commenting.
It sounds funny at face value but I don't see the insight.
If generics are a good idea for a language, then it's better to add them in version 0.01 of the language and build up from there with generics as an intrinsic part of the language and standard libraries.
No, I don't want _tacked on_ generics.
Generics are not in 2021 a "bleeding edge" feature, that _might_ be useful later. They are established and proven in ways that they were not when Java v1 or C# v1 shipped - both added generics in later releases, with associated compromises. Now, you can and should choose upfront if your language needs them, or to choose a different route. There's no need to repeat that history.
YMMV, people have different preferences in programming languages, I have always preferred strongly typed languages. Generics suit me, but pick your lane.
And if you're at version 0.01 of a language, while you're at it, look at being immutable by default, not null by default. Those things can be opt-in for when you need them.
The issue with Go is if you're not shuffling around binary data it has horrible developer ergonomics. Network data is usually untyped, but if you start needing to care about what is in that data and need typed datastructures built around it you stop having fun quick. Same issue with C, really.
It is no wonder that any new format introduced with hype to release developers from type safety boilerplate chains, a few years in production ends up getting its own flavour of schema definition.
Generics were hardly bleeding edge by 2002. Doesn't mean the average developer understood the point, but that's hardly a benchmark, and really most people have a hard time understanding what they have no experience with.
And work on .net generics had been going on since 1998, and by the release of 1.0, generics were going to land in 2.0. It simply wasn't done at that point: "Design and Implementation of Generics for the .NET Common Language Runtime" dates back to early 2001, but by late 2001 the spec was still incomplete and debated (https://docs.microsoft.com/en-gb/archive/blogs/dsyme/some-hi... used to have a link but it's dead).
yes and no. It's clear from the history at the time that they were not a proven, obvious win.
> Generics for .NET and C# in their current form almost didn't happen
> being told by product team members that "generics is for academics only"
https://docs.microsoft.com/en-gb/archive/blogs/dsyme/netc-ge...
My argument is that this particular question has, since then, been settled in favour of generics, outside of academia, and in mainstream languages such as c#. Generics are now "established and proven" in ways that they were not then.
Hindsight: https://twitter.com/matthewwarren/status/920667986108846080
C# is IMHO mainstream, deliberately so. Features in it typically come from other languages. It is a "populariser" of promising programming language ideas for mainstream productivity, not a testbed.
> Doesn't mean the average developer understood the point
at the time as a junior dev, I got the point inside a minute: I was already in the habit of when declaring Customer class adding a strongly typed CustomerList class, for their address, an Addess class, followed by the AddressList, and Orders needs OrderList and OrderItemList. Reducing this repetitive "plug in the type" code to List<Customer> etc was such a clear win.
TBF a common rejoinder back then was that this specific pattern made the opportunity cost of domain-specific operations very low, so you could better those collection interfaces to the specific needs.
Of course 95% of the time they extended the corresponding class or implemented the interface so that wasn't actually true.
Yes, I did miss the "added value" operations declared on that list class, e.g. AddressList typically contained a "GetCustomerPrimaryAddress", etc.
But there were ways to bring that back, by subclassing or (later) extension method on that list. Indeed, 95% of the code was tedious forwarding to the non-generic non-typesafe "list of objects".
Around the same time the dominant pattern of saving or loading data to a data store moved from "active record" where these methods were common, to "repository and DTO" where they were not. Might be co-incidence.
What languages in your opinion have good, non-tacked-on generics? Are they generally better than other languages?
Here are some pretty successful languages that gained generics after maturity: C++, Java, C#, TypeScript (based on JavaScript), Objective-C, even Python.
Most C# libraries in use today were built after generics were added, and there never was a large ecosystem already developed at the time
Java has had lots of libraries thrown away due to initial lack of generics and a lot of painful migration e.g. https://docs.oracle.com/javase/tutorial/extra/generics/conve...
A good parallel would be async-await in JS and nodejs. The entire core library and a huge percentage of the ecosystem ended up being really awkward to use after the introduction of async await. The whole ecosystem is now one big giant mess.
The parsing rules for templates alone probably make a decent door stopper. They can also trivially kill compile times. The bloat from page long symbol names also isn't something to ignore, just std::map<std::string,std::string>::find() results in a decent chunk once the compiler is done expanding it.
> Java
That is a can of worms, haven't professionally worked with Java in some time, but from memory:
* Compile time only, reflection or serialization heavy code will get raw Object types, yay type safety.
* They do not support primitive types, resulting in object boxing and null-ability issues by default as well as third party copy paste libraries that provide collections specialized for primitives.
* You cannot create an object or array using a generic type as that is unknown at runtime, as result some methods require a concrete array as argument just so they can create their own array with the correct runtime type.
* It is fully backwards compatible, so nothing will tell you if you have a compiled library that uses raw types somewhere in your application dumping Integers into a List that should only contain Strings.
* Calling code has to verify object types using runtime casts, you may not want to pay the cost of that.
I could probably go on ...
> Python
Aren't those just hints that the interpreter completely ignores? I have a python 3 toy project that could have benefited from better type checking - the contents of its sqlite database are a mess.
It's cliche, but it's clear that generics like Result ( https://doc.rust-lang.org/std/result/ ) and Option ( https://doc.rust-lang.org/std/option/ ) are fundamental to Rust's design from the start.
> some pretty successful languages that gained generics after maturity
I'm very familiar with C#, of course it is a "pretty successful language", no kidding. I did not say otherwise, I said that there are "compromises" associated with adding generics in V2.
And there are: e.g. starting with collection types, interfaces and base types in pairs, such as List<T> and List, IList<T> and IList, IEnumerable<T> and IEnumerable, etc ad nauseam. Then Delegates that precede Func<T> and Action<T>.
I suggest that you reread my comment above; the point is not just that adding generics later causes complication (historic record is clear that it does).
The point is that generics were _not_ proven in mainstream OO programming when c# 1.0 was released in 2002, but this has changed; now they are. Designs done in the last few years can avoid this complication: Ether your language wants them from the start, or it intends other ways to deal with the same kinds of problems.
In fact, so much research and Go generics are mostly similar to CLU.
ML and its derivatives, Haskell, Rust, Nim.
You see this a lot in web design, everyone thinks they’re an expert at what makes a good website - simply because they are a consumer of them. Even if they only have minimal experience building one IRL.
Yet even C supports lightweight generics, so they must be worth something.
You hit the nail on the head. This is broadly true across virtually everything--at least in most of Western culture (as this is the only one I'm most familiar with).
Anger seems to provoke a more actionable response whereas satisfaction does not (generally speaking). Is someone more apt to call a business over lousy service or great service? Why is it that giving a compliment to someone in customer service sometimes provokes a brief reaction of surprise? I try to do my part as a positive force when I can, but I usually fall short. Besides, is one happy person likely to make that much of a difference over the course of a day? Maybe, maybe not.
FWIW I actually like the idea of generics in Go and have a few use cases where they'd be exceptionally helpful in cutting down the amount of code I have floating around in some libraries. I just don't feel strongly enough about it to jump into a thread to rush to its defense.
It is possible to change though, patiently putting your own focus on positive things, which there are so many of. Takes work, but sooo worth it. Life is more fun that way too.
Go compile times, while great, weren't any novelty to old timers before the .com wave that brought scripting languages into the spotlight and Sun's gigantic Java push.
Thankfully the 20 years detour seems to be getting over.
Go could easily fuck it up tho - that's fair.
When Go first came out, it seemed interesting, but when I checked it out it felt more like throwing out the baby with the bathwater than introducing anything of value. Not every idea since 1972 is bad. So I tried to force myself to use it for a project or two, got frustrated by exactly the things people were complaining about, and never looked at it again.
I haven’t seen any meaningful adoption, so my hypothesis is that by not having the features mainstream users want, Go missed its window of opportunity and the world moved on. It’s neat that they’re adding them now, but.. well.. let’s just say that I was an absolute die-hard Perl fan for a long time so I’m familiar with this pattern.
You must live in a different world. Go has been an immense success since its inception. Cloud, DevOps and SRE are nowadays unthinkable without it.
The only people who care about Go now are the ones who convinced themselves that casting from empty interfaces was okay.
Kidding kidding, please don’t do it kids
I work with programmers from OO background (java) and they can't even grasp the utility of functions as value or closures. Every damn "service" has an interface/generated mock and anemic model. They're desperate waiting for generics for go to "complete".
I fear the influx of OO programmers.
I teach basic go at my job to help people wanting to migrate, they start the go journey thinking go will do "less" because we don't have all features as their main language. Which reminds me of this phrase:
> 'You cannot reduce the complexity of your problem by increasing the complexity of your language.'
1.) In a "simple language" (e.g. without generics) each line of code is easy to read/understand. But reading/understanding the whole program or application is difficult. In "not-simple languages" it's the other way around.
2.) An important criteria to judge the future of a language is how foresight the language authors have. I read that the go authors said in the past that they were not ready to add generics because they didn't know how to do so in a good way. True or not, retrospectively adding generics is not a great indicator for good language design to me. Even if the addition of generics is a net-positive thing, I expect that Go will go in the direction of C++, having a lot of accidental feature complexity in the language.
I think it's better to do it like Lisp and keep a simple (but nonetheless flexible) core. Or do it like Haskell and design the language to elegantly allow as much abstraction as possible, moving carefully towards that goal. For these kind of languages, you better have people who have years long experience in exactly this: designing powerful programming languages. A developer can be the best in their own field, but they are doomed to fail when trying to build a future-proof, well designed programming language on their first attempt. (btw, not relating to the Go author's here)
An example where this didn't work is Angular - from the first moment that I got in touch with it, I knew it was built by amateurs. It's only a framework, but the difference to a programming-language is minor in the case of these kind of all-encompassing frameworks. It is not surprising to me at all that the completely redesigned Angular later on.