> I can't answer a design question like whether to support
> generic methods, which is to say methods that are
> parameterized separately from the receiver.
I work on the Dart language. Dart was initially designed with generic classes but not generic methods. Even at the time, some people on the team felt Dart should have had both.We proceeded that way for several years. It was annoying, but tolerable because of Dart's optional type system -- you can sneak around the type checker really easily anyway, so in most cases you can just use "dynamic" instead of a generic method and get your code to run. Of course, it won't be type safe, but it will at least mostly do what you want.
When we later moved to a sound static type system, generic methods were a key part of that. Even though end users don't define their own generic methods very often, they use them all the time. Critical common core library methods like Iterable.map() are generic methods and need to be in order to be safely, precisely typed.
This is partially because functional-styled code is fairly idiomatic on Dart. You see lots of higher-order methods for things like manipulating sequences. Go has lambdas, but stylistically tends to be more imperative, so I'm not sure if they'll feel the same pressure.
I do think if you add generic types without generic methods, you will run into their lack. Methods are how you abstract over and reuse behavior. If you have generic methods without generic classes, you lose the ability to abstract over operations that happen to use generic classes.
A simple example is a constructor function. If you define a generic class that needs some kind of initialization (discouraged in Go, but it still happens), you really need that constructor to be generic too.
`range` is generic, and by virtue of that is a builtin which only works with a subset of the also builtin magically generic types.
The reason why Go "does not feel the same pressure" has nothing to do with its imperative style[0] it is because they special-cased a few generic structures as builtin very early on, unlike, say, Java (which only had arrays as a typed datastucture).
[0] Java and C# are could hardly be more imperative, hell Java is only just adding anonymous functions
My opinion is that Dart's type system is the optimal type system. It allows for statically typed API surface, but at the same time, I can still add some dynamic voodoo under the surface and enable productive meta-programming.
(1) I started porting our high-performance, concurrent cuckoo hashing code to Go about 4 years ago. I quit. You can probably guess why from the comments at the top of the file about boxing things with interface{}. It just got slow and gross, to the point where libcuckoo-go was slower and more bloated than the integrated map type, just because of all the boxing: https://github.com/efficient/go-cuckoo/blob/master/cuckoo.go
(my research group created libcuckoo.)
Go 1.9 offers a native concurrent map type, four years after we looked at getting libcuckoo on go -- because fundamental containers like this really benefit from being type-safe and fast.
(2) I chose to very tightly restrict the initial set of operations we initially accepted into the TensorFlow Go API because there was no non-gross way that I could see to manipulate Tensor types without adding the syntactic equivalent of the bigint library, where everything was Tensor.This(a, b), and Tensor.That(z, q). https://github.com/tensorflow/tensorflow/pull/1237 and https://github.com/tensorflow/tensorflow/pull/1771
I love go, but the lack of generics simply causes me to look elsewhere for certain large classes of development and research. We need them.
Note that, at least in its current form, the native concurrent map type uses interface{} for all keys and values, and therefore offers no type safety:
https://github.com/golang/go/blob/master/src/sync/map.go
See also: https://github.com/golang/go/issues/18177
All of Go's built-in pseudo-generic types (e.g. maps) require special support from the parser. I'm not sure if they plan on doing that for sync.Map as well, but this is clearly an area that could benefit from generics.
I guess the performance issue has something to do with some missing optimizations. Hopefully it will get better.
Edit: Also https://github.com/golang/go/issues/19361
For (1), for curiosity sake, I tried benchmarking the cuckoo.go file you posted. I'm curious if these numbers are in line with what you found. The first test I did was a Rand test, Putting 10,000 random string values under random string keys, then Getting the same 10,000 keys, then Getting 5000 more random keys. The first line below uses default code you posted, which uses
type keytype string
type valuetype string
The second line specializes the code to just using the string type directly for keys and values. The third line uses interface{} instead for values, which is the code style HN doesn't like. BenchmarkRandInsert-8 100 13474408 ns/op
BenchmarkStringStringRandInsert-8 100 13585071 ns/op
BenchmarkStringVoidStringRandInsert-8 100 14126666 ns/op
That third line shows the cost of casting to/from interface{}. The difference in the code was about as you might expect, with the "void" example needing a cast on each put and a cast on each get.If I use uint32 for value instead, I get:
BenchmarkStringIntRandInsert-8 200 9431584 ns/op
BenchmarkStringVoidIntRandInsert-8 100 10485785 ns/op
The 9.4ms result is using a version of your code hand-specialized for string key and uint32 values. The 10.4ms result uses interface{} for values.I ran a sequential-insert test as well, with keys and values generated sequentially instead of randomly. The results are much the same, though the cost of casting to/from interface{} seems to get mostly lost in the noise.
BenchmarkSequentialInsert-8 100 12185272 ns/op
BenchmarkStringStringSequentialInsert-8 100 12233946 ns/op
BenchmarkStringVoidStringSequentialInsert-8 100 12543980 ns/op
BenchmarkStringIntSequentialInsert-8 200 8823747 ns/op
BenchmarkStringVoidIntSequentialInsert-8 200 8709242 ns/op
I also tried some tests using uint32 as the key type. This int key specialized version is 3x faster than any of the string key versions, but it's not really a fair comparison... a small part of the code is specific to string keys (getinthash), so it's not as obvious what the generic equivalent of that function would be. The keys need to be Hashable or some such, not just interface{}. I'm also allocating and formatting random strings in one case, vs just picking random numbers in the other case.I didn't find the interface{} casting in any of the above code too horribly "gross", just a bit ugly, but that's entirely subjective. Yes, all of the cuckoo versions seemed about 30% slower than the corresponding builtin map type, but my benchmark numbers don't show if that is the price of boxing, of supporting concurrency, or maybe function call overhead, or something else. I'm guessing you did more detailed benchmarks that point to the source of the slowdown.
One last comment: keytype and valuetype really need to be exported, don't they? I wasn't able to use the library without changing them to exported symbols.
> For example, I've been examining generics recently, but I don't have in my mind a clear picture of the detailed, concrete problems that Go users need generics to solve. As a result, I can't answer a design question like whether to support generic methods, which is to say methods that are parameterized separately from the receiver. If we had a large set of real-world use cases, we could begin to answer a question like this by examining the significant ones.
This is a much more nuanced position than the Go team has expressed in the past, which amounted to "fuck generics," but it puts the onus on the community to come up with a set of scenarios where generics could solve significant issues. I wonder if Go's historical antipathy towards this feature has driven away most of the people who would want it, or if there is still enough latent desire for generics that serious Go users will be able to produce the necessary mountain of real-world use cases to get something going here.
I believe your impression of the Go team's position has been corrupted (likely unintentionally) by intermediaries.
But even so, even with all the shortcomings, once Java 5 was released people migrated to usage of generics, even if generics in Java are totally optional by design.
My guess to why that happens is that the extra type safety and expressivity is definitely worth it in a language and without generics that type system ends up staying in your way. I personally can tolerate many things, but not a language without generics.
You might as well use a dynamic language. Not Python of course, but something like Erlang would definitely fit the bill for Google's notion of "systems programming".
The Go designers are right to not want to introduce generics though, because if you don't plan for generics from the get go, you inevitably end up with a broken implementation due to backwards compatibility concerns, just like Java before it.
But just like Java before it, Go will have half-assed generics. It's inevitable.
Personally I'm sad because Google had an opportunity to introduce a better language, given their marketing muscle. New mainstream languages are in fact a rare event. They had an opportunity here to really improve the status quo. And we got Go, yay!
But, I don't know of one, and maybe that's because the Go team is right, some tradeoffs need to be made, and they did, and so Go is what it is. You can't add all the other great features you want and eat the Go cake too.
Disclaimer: I'm no language design expert. Just thinking this from the fact that I've yet to hear of such a language.
> To minimize disruption, each change will require
> careful thought, planning, and tooling, which in
> turn limits the number of changes we can make.
> Maybe we can do two or three, certainly not more than five.
> ... I'm focusing today on possible major changes,
> such as additional support for error handling, or
> introducing immutable or read-only values, or adding
> some form of generics, or other important topics
> not yet suggested. We can do only a few of those
> major changes. We will have to choose carefully.
This makes very little sense to me. If you _finally_ have the opportunity to break backwards-compatibility, just do it. Especially if, as he mentions earlier, they want to build tools to ease the transition from 1 to 2. > Once all the backwards-compatible work is done,
> say in Go 1.20, then we can make the backwards-
> incompatible changes in Go 2.0. If there turn out
> to be no backwards-incompatible changes, maybe we
> just declare that Go 1.20 is Go 2.0. Either way,
> at that point we will transition from working on
> the Go 1.X release sequence to working on the
> Go 2.X sequence, perhaps with an extended support
> window for the final Go 1.X release.
If there aren't any backwards-incompatible changes, why call it Go 2? Why confuse anyone?---
Additionally, I'm of the opinion that more projects should adopt faster release cycles. The Linux kernel has a new release roughly every ~7-8 weeks. GitLab releases monthly. This allows a tight, quick iterate-and-feedback loop.
Set a timetable, and cut a release with whatever is ready at the time. If there are concerns of stability, you could do separate LTS releases. Two releases per year is far too short, I feel. Besides, isn't the whole idea of Go to go fast?
I hate generics. also, I hate exceptions.
Too many people are wanting "magic" in their software. All some people want is to write the "Happy Path" through their code to get some Glory.
If it's your pet project to control your toilet with tweets then that's fine. But if it's for a program that will run 24/7 without human intervention then the code had better be plain, filled with the Unhappy Paths and boring.
Better one hour writing "if err" than two hours looking at logs at ohshit.30am.
This is sampling bias at work. The people who need generics have long since given up on Go and no longer even bother participating in Go-related discussions, because they've believe it will never happen. Meanwhile, if you're still using Go, you must have use cases where the lack of generics is not a problem and the existing language features are good enough. Sampling Go users to try and find compelling use cases for adding generics is not going to yield any useful data almost by definition.
Not implementing generics, then suggesting that it would be nice to have examples of generics being used in the wild… You had it coming, obviously.
Now what's the next step, refusing to implement generics because nobody uses it?
> Every major potential change to Go should be motivated by one or more experience reports documenting how people use Go today and why that's not working well enough.
My goodness, it looks like that is the next step. Go users have put up with the absence of generics, so they're not likely to complain too loudly at this point (besides, I hear the empty interface escape hatch, while not very safe, does work). More exacting developers have probably dismissed Go from the outset, so the won't be able to provide those experience reports.
https://stackoverflow.com/questions/43368604/constant-struct...
https://stackoverflow.com/questions/18342195/how-to-declare-...
This is a remarkable oversight which makes it impossible to write purely-functional code with Go. We also see this same problem in most other imperative languages, with organizations going to great lengths to emulate const data:
https://facebook.github.io/immutable-js/
Const-ness in the spirit of languages like Clojure would seem to be a relatively straightforward feature to add, so I don't really understand the philosophy of leaving it out. Hopefully someone here knows and can enlighten us!
But take a look at this article about the design goals of Go: https://talks.golang.org/2012/splash.article Look especially at section 4, "Pain Points". That is what Go is trying to solve. So what the Go team is asking for, I suspect, is concrete ways that the lack of generics hinders Go from solving those problems.
You say those aren't your problems? That's fine. You're free to use Go for your problems, but you aren't their target audience. Feel free to use another language that is more to your liking.
Note well: I'm not on the Go team, and I don't speak for them. This is my impression of what's going on - that there's a disconnect in what they're asking for and what the comments here are supplying.
(And by the way, for those here who say - or imply - that the Go team is ignorant of other languages and techniques, note in section 7 the casual way they say "oh, yeah, this technique has been used since the 1970s, Modula 2 and Ada used it, so don't think we're so brilliant to have come up with this one". These people know their stuff, they know their history, they know more languages than you think they do. They probably know more languages than you do - even pjmlp. Stop assuming they're ignorant of how generics are done in other languages. Seriously. Just stop it.)
for foo := range(bar)
Instead of: for _, foo := range(bar)
When you just want to iterate over the contents of a slice and don't care about the indices. Failing to unpack both the index and the value should be a compile error.Improvements to package management is probably the highest item on my wishlist for Go 2.
Working with batch processing I'm often changing my maps to lists or hashes multiple times during discovery. Go makes me rewrite all my code each time I change the variable type.
But well, I trust the core team to make the best choices.
What I'd like is some kind of automatic error propagation, so any unhandled error will return from function wrapped with some special class with enough information to find out what happened.
Collections?
It also really makes me wonder if I am living in some sort of alternate reality. I am a professional programmer working at a large company and I am pretty sure that 95% of my colleagues (myself included, as difficult as it is for me to admit) have no idea what a reified generic is. I have run into some problems where being able to define custom generic containers would be nice, but I don't feel like that has seriously hindered my ability to deliver safe, functional, and maintainable software.
What I appreciate most about Go is that I am sure that I can look at 99% of the Go code written in the world and I can understand it immediately. When maintaining large code bases with many developers of differing skill levels, this advantage can't be understated. That is the reason there are so many successful new programs popping up in Go with large open-source communities. It is because Go is accessible and friendly to people of varying skill levels, unlike most of the opinions expressed in this thread.
For example, in Go it's difficult to implement state machines in a clean way. Another is handling events and timeouts is easier with Reactive programming. Distributed programming is easier with Erlang or Akka. Don't wait for problem reports in the Go community. Look at the problems in other PLs and proactively improve Go.
Turning: func (f Foo) name() string
Into: func name(f Foo) string
Callable like this: f.name() or name(f)
Extending foreign structs from another package should be possible too, just without access to private fields.
Other than that, if-as-expression would be nice to have, too.
This post really frustrates me, because the lengthy discussion about identifying problems and implementing solutions is pure BS. Go read the years worth of tickets asking for monotonic time, and see how notable names in the core team responded. Pick any particular issue people commonly have with golang, and you'll likely find a ticket with the same pattern: overt dismissal, with a heavy moralizing tone that you should feel bad for even asking about the issue. It's infuriating that the same people making those comments are now taking credit for the solution, when they had to be dragged into even admitting the issue was legitimate.
(Sorry just discovered this song a few days ago)
in particular, i think it could help with the method dispatch, but probably not with the memory allocation ( although go already uses interfaces pretty extensively).
I like Go, but the maintainers have a maddeningly stubborn attitude towards generics and package managers and won't ease up even with many voices asking for these features.
This is absurd. Waiting to fix a known language design issue until a production outage of a major customer is a failure of process, not an achievement. The fact that the post presents this as a positive aspect of Go's development process is beyond comprehension to me.
Shows the value of constantly being transparent and supporting open source projects.