Summary of Go Generics Discussions
docs.google.com
docs.google.com
> The generic dilemma is this: do you want slow programmers, slow compilers and bloated binaries, or slow execution times?
I don't think these are the only outcomes, but lets go with it. A slightly slower compiler then. Solved.
> The generic structures are less useful than a concrete one.
If they weren't more useful then nobody would be arguing for them.
> Generic structures can be less optimized than a concrete solution
Ridiculous.
>Code won’t look idiomatic.
Idiomatic is a function of the language syntax and features. If generics are a feature then their use will, by definition, be idiomatic.
And on and on. At this point people using Go accept the lack of generics (and, oddly, sometimes wear the omission as a badge of honor). I don't think anyone happily using Go is expecting generics anytime soon, if at all. And I don't think the authors need a paper to show their dislike for them. Who are they trying to convince anyway?
EDIT: A few folks have told me I come across too heavy handed in my reply. I apologize to and don't mean to discourage the author(s)! It's a tough document to write. Especially if this was just a starting "brain dump." The document title had just set my expectation towards a neutral tone.
Those using Go without generics will go on using it.
I would also posit the following though:
Those that don't use Go because it lacks things like generics aren't likely to use it once they are added either.
My personal opinion is Go is a simple language with little to no features of significance. This makes it easy to learn and easy to write. However, it leaves programmers used to higher level and more powerful constructs wanting. If you are used to writing Haskell or even something like Ruby then Go is going to feel like a pretty significant step down. God forbid you are looking at a language like Rust which makes Go look skinny in terms of features indeed.
At the end of the day it boils down to what you are happy with using. Is a hammer and nails good enough for you? Or do you prefer a variable speed handheld drill with impact mode and keyless chuck, etc etc etc?
No disrespect meant to Rust, but it's hardly as simple as saying, "Go lacks Generics! Use Rust!" Rust is far --- far --- from production-worthy. It's certainly settling down into a 1.0, but it has had an extremely volatile standard library and design. There are some early contributors who are doing the "brave work" of using it in production with the aim of bringing it forward to a 1.0, but for the rest of us, it's simply not even in the consideration set.
There's also a real question about garbage collection. Rust's "feature set" is surely thicker than Go's - but you need some of those things because you're doing much more work at compile-time. We can argue about the runtime cost of a mature GC (and Go isn't even there yet!), but again, it's not as simple as "use Rust." There's a real productivity cost, I would argue, with doing that extra work and adding that complexity.
It doesn't boil down only to what you are happy with using. For the weekend project, it really doesn't matter at all. For a large, production application, there are real, important questions to answer and they matter a great deal. It's not "is a hammer good enough?" - A hammer is hardly good enough when you're staring at screws.
I'm not saying one is right or one is wrong. I'm saying choosing a language is usually only something that matters at a certain scale, and at that scale, you have to be very knowledgable and careful.
I was saying if you are evaluating Rust then Go probably looks very feature light in comparison.
Same goes for programmers currently using Java/C# or even Haskell. Go is just a lot less featured than other high performance managed languages that are just as battle tested and just as fast.
This is not to say that it's not a good language or the correct tool for a job. Only that those programmers will be left wanting in many areas, generics only being one.
Fact is, the golang authors are using generics (and polymorphism, and ...) in the language and in the compiler. So they have answered all the questions about generics :
> does it result in a slow compiler ?
Obviously not, after all the compiler has generics.
> are generic structures less useful ?
Well how useful do the Go authors find map/slice/arrays/... the generic Go datatypes ? So obviously this is both a bullshit argument and one people like Rob Pike don't actually believe is true.
> Generic structures are less optimized
If this is true, then clearly Go's authors find this an acceptable cost.
More generally, let's take a look at Rob Pike's example code. For instance here is a talk described as "An hour-long talk delivered by Rob Pike at Google in October 2009. The language's first public introduction."
http://talks.golang.org/2009/go_talk-20091030.pdf
First code example that isn't hello world, first function called :
if len(str) > 0 { ch = str[0] }
A polymorphic function ... weekend := []string{ "Saturday", "Sunday" }
timeZones := map[string]TZ {
"UTC":UTC, "EST":EST, "CST":CST, //...
}
Generic data types.Why are we arguing about this ? There is no disagreement between Go's users and authors : Go needs generics. The authors simply don't feel capable of implementing it.
How about, like hundred other problems in Go we simply ask ourselves "is this hard to program in a compiler ?", and yes it is. So they put in an ugly hack for their own special cases. This argument, first of all, consistently fits all Go languge decisions leading to the correct conclusion, as opposed to all the arguments given by the Go authors that have no end of exceptions and personal preferences to take into account to arrive at the correct conclusion. Go's authors clearly have some trouble understanding the mathematical foundations of type systems, which is plainly visible in Go's type system. It's inconsistent. Without massive holes in the language, like we currently, this will become VERY visible and move people away from Go.
That said I would disagree that flaws in a type system will result in people moving away from Go. Scala has a type system that is ridiculous complex and has plenty of holes and it doesn't seem to have affected its adoption rate.
>"Go's authors clearly have some trouble understanding the mathematical foundations of type systems"
Well, yeah. Is there anything wrong with that?
For these developers Go can be a good fit. Everybody knows it doesn't have feature parity with Haskell et al from a computer science perspective.
If that's all that mattered we would be seeing a lot of Haskell adoption wouldn't we?
You shouldn't pick a language based on a checklist.
All of which are orthogonal to having a more modern language, except maybe simplicity.
And even for that, after a while you understand that not having, say, generics, pattern matching or some such language feature, is not "simpler" is just having to do more busywork manually and in an error prone way.
There's simplicity as in "this is simple to understand and use" and simplicity as in "we ommited some advanced functionality and you'll have to built it by yourself, every time". The second is a false simplicity.
As Einstein put it: "we should make things as simple as possible, but not simpler".
Pointing to a specific point were making something simpler makes it lose the essense it should have.
And the analogy breaks in that all German speakers will understand the former.
Yeah, it's a known fallacy. We should replace "simplicity" and "readability" with understandability, since we don't really care about the first two, but only confuse people.
Readability: I'm pretty sure having to create copies of container classes for each thing they can contain, or casting in and out of object, or wrapping all your contained classes in an interface, is less readable than generics, not more readable.
Compile time: this is an overblown concern. If you're using a compilation system that compiles only the parts you change, your typical compilation should be so quick as to take negligible time. Complete rebuilds such as on a CI server should be happening asynchronously to development and therefore should only cost server time, which is of negligible value in comparison with development time. If your builds are taking so long that server time becomes an issue, your code has built up too much cruft and it's time to refactor to reduce your size (failure to do this will cause much bigger problems than slow compilation).
Language simplicity: I'm pretty sure having to create copies of container classes for each thing they can contain, or casting in and out of object, or wrapping all your contained classes in an interface, is less simple than generics, not more simple.
Toolchain simplicity: I am not able rightly to apprehend the kind of confusion of ideas that could provoke someone to think this is relevant.
Maintainability: I'm pretty sure having to create copies of container classes for each thing they can contain, or casting in and out of object, or wrapping all your contained classes in an interface, is less maintainable than generics, not more maintainable.
In particular, take a look at section 5, "Dependencies in C and C++." From that section:
The construction of a single C++ binary at Google can open and read hundreds of individual header files tens of thousands of times. In 2007, build engineers at Google instrumented the compilation of a major Google binary. The file contained about two thousand files that, if simply concatenated together, totaled 4.2 megabytes. By the time the #includes had been expanded, over 8 gigabytes were being delivered to the input of the compiler, a blow-up of 2000 bytes for every C++ source byte.
As another data point, in 2003 Google's build system was moved from a single Makefile to a per-directory design with better-managed, more explicit dependencies. A typical binary shrank about 40% in file size, just from having more accurate dependencies recorded. Even so, the properties of C++ (or C for that matter) make it impractical to verify those dependencies automatically, and today we still do not have an accurate understanding of the dependency requirements of large Google C++ binaries.
The consequence of these uncontrolled dependencies and massive scale is that it is impractical to build Google server binaries on a single computer, so a large distributed compilation system was created. With this system, involving many machines, much caching, and much complexity (the build system is a large program in its own right), builds at Google are practical, if still cumbersome.
I think that Pike makes a convincing case that fast compile times at scale are a reasonable requirement for their needs.
1. Talking about C++ doesn't prove anything about the effects of generics on compilation time. C++ doesn't have generics, it has templates. Also, C++ is a particularly difficult language to compile quickly--it shouldn't be hard to compile a language faster than C++, even if you completely ignore templates/generics. If you want to make a reasonable comparison to Go's compilation, you should be looking at Java or C#. I've worked on an 800,000 line Java project and a 500,000 line C# project (the second made very heavy use of generics). The first took about two hours to build completely from scratch, the second, 40 minutes (both on not-particularly-powerful desktop workstations--the difference in time also is affected by the fact that there was 5 years between these projects). And like I said, typical builds of only changed code took <30 seconds.
2. I already said this, so I'll just quote myself: "If your builds are taking so long that server time becomes an issue, your code has built up too much cruft and it's time to refactor to reduce your size (failure to do this will cause much bigger problems than slow compilation)." 4.2 megabytes of code means that no one can reason effectively about the system, especially in a language as dense as C++.
> This document is a distillation of convoluted generics discussions and it is not an official opinion of the Go team, although it does incorporate their opinions.
I think the document simply amalgamates all the points that have been made on both sides of the debate. I would agree that some of the points made against the generics do seem to be somewhat biased (or debatable or flimsy/whatever), but I think the document itself merely tries to collect all of these arguments together, so that when someone says, "what is the argument against this point?", people can point to this document and say that someone raised the argument that "The generic structures are less useful than a concrete one". I think then that can be used as the starting point for discussion (someone can raise the counterpoint that they think this is wrong).
I see this as separating the discussion of the validity of the points from an enumeration of the points made so that people can refer to them (which I think is helpful). In addition, with all the arguments for and against collected together, people can more effectively discuss the proposition.
EDIT: I am all for go having generics, I just don't think the document itself is biased.
Except that a capital-F Fast compiler is a design goal for the language, so that's the _least_ desirable of the three. I feel tempted to go with slower execution times, simply because it's already a part of the design as well: you can't optimise too aggressively on a limited compilation time budget.
I wouldn't assume that the language as is is totally devoid of features that already complicate compilation -- that would probably result in some kind of polish notation proto pascal…
So it's all about tradeoffs, and the go designers just say that this isn't worth it, at least for the current time and the current Plan9-ish Thompson compiler. Whether this crosses over from the pragmatic to the dogmatic is up for discussion (cf. dynamic linking).
Basically, I'm saying that if we're going to talk about compiler performance - and we should, since as you point out, it is a design goal of Go - we need to be a bit more concrete. Based on everything I know about Go's goals, they would be particularly worried about solutions that don't scale.
Seems like a pretty stupid approach to ignore any language additions if they have any potential to impact the design goals. Implement it. Benchmark it. Then decide if it should be landed into mainline builds.
Except the compiler is nothing to write home about in the "fast" department (others, including D are just as fast), and modular compiling and a good design of linking etc can solve the compile speed issue far better, and without sacrificing more important things...
Besides, this only means they don't understand diminishing returns and opportunity cost.
If I'm honest though, most of the time I don't miss generics. However the moment I start using a database or parsing JSON documents (particularly ones you cannot make assumptions about the document structure), the lack of generics is a massive pain.
This is part of why the Go generic debate rages on, while Go itself is continuing to be used. If Go were lacking in any "generic" associative lookup structure it would get no traction in 201x. (If C were introduced today in a world that somehow lacked it, nobody would give it a second look. Which is as it should be... one would hope we've learned something in the past 40 years....)
I'd also question whether maps are a good example either since a map[string]interface{} would be incompatible with a map[int]interface{} (which leads to many of the code duplication examples that developers demonstrate when discussing the need for generics in Go)
Maybe I'm splitting hairs, but this would likely be the mindset that Pike et al have with regards to "generic" built in types.
I do agree with you that there is "some level" of generics on Go though: interfaces{}. Sadly that feature was "uglified" by the constant need for casting (and that's putting it politely).
The map is what is generic, not a specific map. List<Int> is not "compatible" with a List<Char> in any implementation of generics; how could it be? What would that even mean?
There isn't a good way of taking a "map", just any "map", but that's a separate problem. Not all languages that have generics can do that, either, only some of them.
(A recurring issue in these discussions about Go is that people end up bringing the union of all possible things that generics have ever done, anywhere, to the party, but in reality no language actually has all of those features at once. Real generic implementations, when not being discussed in the context of Go, are usually being discussed in how they are broken. This is what ultimately brought me peace in this argument... in the abstract, it's totally obvious that Go ought to have generics. In the concrete it's a great deal less obvious, and real implementations all do indeed have their serious problems once you get down to the nitty-gritty of what assembly instructions they would actually execute, and exactly how the compiler would produce them. It's all fun and games when you're just waving hands around.)
interface{} isn't a "generic" in any sense.
Well, arguably there are languages where that's possible. PHP does some odd things with maps and arrays, and Perl hashes don't have any distinctions between strings, integers nor floats (or at least none that I've run into - please correct me if I'm wrong). Though I will grant you that I'm now comparing strongly typed languages to weakly typed ones.
I think my problem here is although I cut my teeth on statically typed languages, my exposure to generics in those languages was rather limited (in fact does Pascal even support generics?). So I'm possibly misunderstanding the usage of generics in modern strongly typed languages.
In 2015, your new language needs to have some web stuff built into the standard library or at least some foundation libraries. I don't know that that makes it web focused though.
Seems like there is a huge cultural gap or something, Go was initially built by real-deal C guys. There is a ton of overlap with them and like the plan-9 and suckless fans. If you're not doing hard-realtime or poking registers, it very very much looks like a language designed to fix the problems C has. Just about any of the standard UNIX tools could fairly easily be built in Go and probably be cleaner, maybe more bug free. When the go compiler and run-times are optimized up and the performance is there, it seems like you'd really need a compelling argument to not write those kinds of tools in Go instead of C. If you really dive in to the plan-9 and suckless stuff, how many of those programs legitimately have more than a couple data-structures? They are small, they do one thing, they try to do it well. There are a lot of "missing" things that they aren't urgent the replace because of that, like dynamic linking...
What's really amazing is that it has been drawing the ruby, js and python guys in. And generics is a/"The Big" hangup? Why not just the fact that you actually have to compile it or that the compiled product has an OS and hardware requirement.
Web-focused is a poor choice of words on my part. But IIRC Google adopted the project as a means to speed up development and execution time for their web services back ends (if that's wrong then I'll happily welcome a correction).
Either way, Go does have a presence in web development - not least of all by Google
> What's really amazing is that it has been drawing the ruby, js and python guys in. And generics is a/"The Big" hangup? Why not just the fact that you actually have to compile it or that the compiled product has an OS and hardware requirement.
Only the compiled binary is OS dependant, the code is just as portable as your exampled languages. And the only hardware requirement is the word size and architecture of the CPU - which for web development would nearly always be x86_64. Plus Go makes cross-compiling child's play - so there's no issues with compiling for another CPU architecture (if, for some reason, you're targeting ARM over x86) or for 32bit platforms, Windows, FreeBSD, OS X or Linux.
I actually think the fact that Go needs compiling is a real strength of Go. Unit and regression testing of websites can be painful in JIT compiled, dynamic languages; where as go build catches a lot of developer "absent-mindedness" before the web application even reaches the testing phase. Obviously compiling isn't going to catch everything, but in my experience, it has massively streamlined the process of developing production ready code.
I certainly do. Go replaced Ruby as my language of choice some three years ago but the lack of generics have often prevented me from properly reusing existing code. I would totally love to see (ideally Haskell-like) generics introduced, the sooner the better.
Lots of people don't remember this, but there used to be a whole host of such tools in the early Java days when the language was more terrible than it is now. IIR there's a bunch of similar stuff for C as well.
I guess a modern analog would be something like CoffeeScript which fixes all sorts of issues with Javascript.
More seriously, this would make sense as a prototyping step (I think the Java equivalent was called Pizza?), but ultimately this would be a fork of the language, with all that implies.
[1] http://www.slideshare.net/wuvist1/metaprogramming-go-with-py...
Evangelism.
I'd agree, but for the "by definition". "Idiomatic" is a question of typical patterns of use. This is "a function of the language syntax and features", but that function is not a simple or static one and involves the programming community around the language. It's certainly possible for a feature to fall out of favor and its use to be unidiomatic.
For example, Ruby for loops are part of the language, but there use is pretty universally considered unidiomatic.
If, in an interview, someone wrote Java code using ArrayList instead of ArrayList<T> or C# code using List instead of List<T>, I wouldn't hire that person. If you write code in Java or C# that doesn't use generics, you're giving up type safety, performance, readability, reusability... the list goes on. Your code is bad and you should feel bad.
Not having generics in Go means that your code will be less type-safe, less performant, less readable, less reusable, etc. than comparable code in Java or C#.
I'll go one step further and say it was short-sighted for the Go developers to implement data structures without generics in the first place. Yes, it makes it harder to implement the language initially, but that's an up-front investment of time that will prevent millions of lines of bad code from being written if the language gains traction. Users who really insist on shooting themselves in the foot can do GenericType<Object> and make their bad decision explicit. It's just bad design for a modern statically-typed language not to include generics (or a comparably powerful type abstraction).
I find that's a very good rule in those discussions, it's too easy to get bogged down to some obscure use case and miss the forest for the trees.
I note however that the authors seemed to have forgotten about that rule just after writing it down since the rest of the document is full of vague assertions and unsubstantiated arguments like:
- "I have heard of simple libraries having text segments shrink from megabytes to tens of kilobytes by revising or eliminating the use of templates."
- "Generic algorithms can be harder to follow than their non-generic counterparts."
- "Fancy algorithms are slow when n is small, and n is usually small. - Rob Pike"
- "map/slice suffice for most of the cases encountered in programming. (based on some community opinions https://groups.google.com/d/msg/golang-nuts/smT_0BhHfBs/MWwG.... The linked thread contains a bunch of posts, not sure what I should be looking for (I thought the whole point of this document was to "go over the relevant points without digging through hundreds of forum posts"?)
- "Code won’t look idiomatic." I'm not sure what that even means.
I don't know if generics have a place in Go or not but I'm not sure this document is going to help solve that issue. Making a list of concrete use cases for generics and discussing possible alternatives would probably be more constructive.
I feel I need to remind you, and a lot of the other commenters here, that this is a top-level summary of all the various arguments. And that it is possible on the Internet to mention arguments without actually advocating for them at the same time. I doubt you could find any individual on the planet that would "agree" with that entire document from top to bottom.
I think the better reaction to simias and my point is that this is clearly a work in progress. I assume that in the future, it will actually contain the use-cases I thought it would.
How many man-hours have been spent arguing about this?
And by "stabbing at it" I mean opening up the compiler to see how difficult it would be, not a commitment. But even for that, the best answer right now is to wait for the in-progress compiler rewrite to get further along.
> A vector of bytes uses significantly more than one byte per byte.
There is still the option to use an array of bytes (the primitive data type, not the class Byte) if the performance of boxing/unboxing is a problem.
In fact, i don't understand how the vector example is relevant to generics at all. Even pre-generics the underlying storage for a Vector in java would be an Object array so the boxing/unboxing would still happen?
So one option is to "box" everything, which basically amounts to saying "I will take a pointer to everything", and in the case of Go, "I will take the pointer to that thing's type". (That's how interfaces work in Go.) Now everything is the same size (pointer-sized). But now everything's boxed. Alternatively, you can compile lots of functions, but now everything's compiled in separate copies of the function.
From the high-level language POV it all seems so simple. When converting it into actual assembly, which has to interact with the rest of the system, it gets more complicated, and the performance implications of even slight slowdowns to a function call can be catastrophic given how many of them we make.
Personally I'm still interested in the question of whether it's feasible to create a different calling convention into a generic function that deals with this directly. This might not be possible in C due to its age, or C++ due to its reverse-compatibility heritage, but I'm interested in whether we can write a sufficiently-generic (in the more general sense of the term) function calling system that it can work with Go better. Basically looking at types like a vtable and abstracting out all the calls that are size-dependent into vtable-lookups, which ought to be slower but only slightly slower than a direct call (given that it seems like everything can be resolved at compile time except the actual address). But I don't know. Interested to hear if anybody else has ever tried that or anything similar. (Assembler is a great deal more flexible than the structured programming we have laid on top of it, at least in theory.)
For what i've been using java for it's never really bothered me. When there is need to use primitives it's almost always in the form of a list where you can just use an array instead to avoid the boxing/unboxing.
The risk of null pointer exceptions when implicitly unboxing objects are more annoying..
If anyone can come up with an implementation that works, I see no reason why the team would say no to its inclusion.
This kind of discussion is actually something that bothers me even more than generics at the moment : https://groups.google.com/forum/#!topic/golang-nuts/m7k9Epxq...
Come on... removing an item from a list requires tricks ?? I know go aims at being memory efficient, but delete(list,obj) should be idiomatic.
That's not a list, it is a slice. Which is a slight abstraction over an array. You the programmer are directly responsible for the size of the slice storage, and how many elements it currently contains.
I know go aims at being memory efficient, but delete(list,obj) should be idiomatic.
If this ends up being a common operation, I could understand arguments to add it to the language. But in general if you are adding / removing items a lot, you'd be better off using an actual list data structure instead.
http://golang.org/pkg/container/list/ , with its "Element" wrapping a "Value" property of type interface{} seems really weird for such a basic need. It means that whenever you're storing an object into the list, you need to remember the element in addition to the original object to be able to remove it later...
A general principle in interface design for data structures is don't make expensive things easy. So, if removal from a slice is O(n), then the data structure is obviously not optimized for that, so don't provide a function for it.
Well, i may be spoiled, but ever since i stopped working with C, i never had to deal with data structure so different between arrays and list than go (that is, slices and container/list).
The performance concern is an absolute hypocrisies. Whenever you decide which structure you pick in eg, Java, you know that the various containers are optimized for various operation, and you decide which to use depending on performance only.
In go the language is so broken (because of the type system) that using a chained list vs an array makes your code look completely different.
Actually there are multiple options for delete from a slice. If you need to preserve order then it's going to be an O(n) operation, and if you're deleting often maybe it shouldn't be a slice but some other structure. There are two options for delete mentioned in that document:
Delete
copy(a[i:], a[i+1:])
a[len(a)-1] = nil // or the zero value of T
a = a[:len(a)-1]
Delete without preserving order a[i], a[len(a)-1], a = a[len(a)-1], nil, a[:len(a)-1]But it would actually lead to less accurate Type-safety than if one had a different sort method for each sortable type, it would lead to some amount of "duplicated" code but that is a different matter than Type-Safty.
Both C and Go can have (and, in fact, it has been done in both) some degree of generics implemented in a preprocessor-based way using only features built into the language and compiler.
So, again, I would maintain that "no generics" is no more valid of the Go status quo than it is of the C status quo.
The whole continued argument is odd. The "whole generics are important" and "Go is too simple" thing seems counterintuitive when all of the comments mention Rust, Haskell, Java, etc., yet the proponents of generics are not using those languages instead.
Why if Go does not have the features one needs, would a person be compelled to use it?
I'm writing a lot of production Go code, and I'm very happy about the language. I wouldn't put generics at the top of my wish list but at times I wish I had them. But not enough that I'm willing to use a 3rd party solution, so I guess not that much :)
One of the things you learn when using go is that generics are not the answer to everything. They are certainly the answer to some specific things, like optimal implementations of common algorithms.... But not every application needs to use the A* algorithm, or a left leaning red black tree... Would I like to have generic implementations of those that I could just toss in my application with my own types? Sure. But I've done enough generic programming to remember when you could finally stop writing
foo<bar<baz, bat> >
And could finally do foo<bar<baz, bat>>
.... And I don't really want to have to write that kind of code again.