Seemed like a LOT of work (basically implementing the same sort that was 99% identical for every struct) or use weird reflection-workarounds to get this to happen. In Java I would not even given this a second thought and be back to coding up the important part of the code ages ago.
I am a new go-lang user so would love to know what the best approach to resolve this is without a) repeating the same thing for every struct, or b) relying on "unsafe" reflect techniques (since AppEngine rejects code that does that) - surely sorting structs is a super-common, basic thing for a systems language? I've seen someone just nonchalantly say "Use interfaces" but I'm not sure still.
I like the language generally but this is a real "WTF?" moment for me.
I suppose that we will see more and more code generators which will practically remove the need of generics. We already use them without complaining for serialization in JSON/Protocolbuffer/etc...
It's definitely acceptable to use a workaround for a missing feature once or twice in a language, because no language is perfect, and no language benefits from being burdened with all features imaginable.
But if a workaround becomes part of the day-to-day workflow, then you are likely using the wrong language.
Examples could be: using (textual) code generation for generics, or using type annotations throughout a dynamic language project.
The Go community doesn't hate protocolbuffers, but it does tend to think that generics are evil and shouldn't exist.
I'm certain that generic generators will be shunned by the community at large, due to solving a problem they don't believe needs solving. Without the network effect to build up a user and developer-based, they'll languish in obscurity.
I sure would like to be wrong, though!
The generator given as an example in the official blog [0] is called "stringer". It is made of 639 lines [1] mixing ast parsing/traversing and printf statements. If this is what I am supposed to write then of course copy-pasting becomes a pragmatic alternative.
[0] https://blog.golang.org/generate
[1] https://raw.githubusercontent.com/golang/tools/master/cmd/st...
Not saying that it's pretty, but it's quick and easy.
Numbers and strings support <, but there's no way to specify operators in interface, and no way to specify operators for other types anyway.
You could do
type Comparable interface {
Compare (x Comparable) int // -> -1, 0, 1
}
func Sort (x []Comparable): []Comparable {
/// ...
}
And whilst this Sort could work, how do you call it? []int isn't []Comparable, and can't be converted to one: you have to make a new array. Then, when you want an array of ints on the other end, you have to convert it back, which now involves run-time type assertions.Even an array of something that implements Comparable isn't compatible - it can't be, because Go doesn't know Sort won't take Bar[] and put a Foo in it, if Foo and Bar both implement Comparable.
And, whilst you can define Compare for int, the other argument will be a Comparable, not a int, so you'll have to have a run-time type assertion for each comparison.
Conversely you might try
func Sort (x []interface{}, f func(interface{}, interface{})int) []interface{} {
/// ...
}
You still can't sort []int, and your comparison function can't know it's receiving int, so it will have to type-assert both arguments at each comparison.It's slower than doing it yourself but for normal-sized slices you shouldn't notice.
Also https://github.com/bradfitz/slice which is similar but faster and relies on more unsafe black magic.
Or is that the "unsafe" reflection you're talking about?
EDIT: I'd argue what we need in Go is something like a type-class for interfaces, so we can match on fieldsets the struct contains, and not just methods.
> I wanted to see how hard it was to implement this sort of thing in Go, with as nice an API as I could manage. It wasn't hard.
> Having written it a couple of years ago, I haven't had occasion to use it once. Instead, I just use "for" loops.
> You shouldn't use it either.
+ People keep telling me we should be able to implement a map function in go.
+ I implemented a map function in go.
+ The map function was ugly, slow, unsafe and generally an abomination.
Conclusion? You don't need a map function in go.
You may not agree but you have to admire his dedication to the One True Way whatever is put in his way. Even if it's him that's erecting pretty impressive roadblock himself.
Sure, some people are productive in it and that's fine - but for those of us who have worked in better languages taking the step down is unattractive.
Tremendous. The amount of irony in that could fill a steel furnace for a year.
// goodFunc verifies that the function satisfies the signature, represented as a slice of types.
// The last type is the single result type; the others are the input types.
// A final type of nil means any result type is accepted.
func goodFunc(fn reflect.Value, types ...reflect.Type) bool
Dear god, why?!I don't say this in anyway to eulogize Go: In some ways, Go is pathetically unexpressive. That said, it currently fills that gap for writing middleware between C sacrificing too much developer productivity and Perl/Python/Ruby/PHP sacrificing too much performance. Generics would be nice to have for this core use case for Go, but it's probably not critical.
I might agree if the submitter wasn't a person on the Go team trying to highlight some recent thinking ;)
Simple means composed of a single element, not compound, unentangled, being the opposite of "complex", which of course means consisting of many different and connected parts. Instead Go people prefer the term to mean the now more popular meaning, which is "easy to understand, familiar".
I think a parallel can be drawn with another word from the English language: "free". You see, English doesn't have a word for the latin "liber" (libre, at liberty), like other romance languages have and I can name at least Italian, Spanish, French and Romanian (my own tongue). In these romance languages there's a firm distinction between libre and gratis, whereas in English there's no adjective signifying liberty without also meaning "at no monetary cost". I find that to be interesting and I bet it happened most probably because at some point in time these 2 issues were correlated.
Back to simplicity, while you can often correlate simplicity with easiness, mixing the terms is unjust because sometimes simple things aren't easy at all and sometimes easy things aren't simple at all (much like how sometimes gratis things are restricted and liberties aren't gratis). Speaking of which in my own native tongue the word for "easy" is also used to signify weightlessness (light, soft). Makes sense in a way, but you can see how language works.
And it would be a shame to lose the true meaning of "simple", just because its usage is at hand when trying to describe things that are or are not desirable. As in the end, this is how the meaning of words gets diluted and lost: because of our constant desire to make other people believe our ideas and buy our shit. So we exaggerate. Just a little, after all, we are only going to end up with a language that's ambiguous. What does it matter if "open source" has had a clear definition since OSI and that it wasn't in use before that, it's marketable dammit, so lets use it for all shit that has source-code available. Etc, etc.
And I get it, saying that generics aren't easy isn't so appealing, because that would be an acknowledgement of one's own capabilities and knowledge, being a metric relative to the one who's speaking. Whereas simple is an objective metric, with things being simple or complex irregardless of the person that's passing judgement. Still, generics are only as complex as static type systems. When you have a statically typed language, generics are a natural consequence. And if you don't add generics, then you need to add an "Object", "Any", "dynamic" or whatever you want to call it, which would be a whole in that static type system, not to mention introducing special cases (like the builtin arrays and maps) and that's complex by definition. Java did as well, introducing generics at version 5, when it was too late to do it cleanly and the result isn't nice at all ;-)
Agree that nothing should be a hard and fast rule. But the significance of DRY is that it increases quality. Not even having the option to "stay DRY" for many problems implemented in Golang will increase bugs. That's just how it is.
Given the tradeoff between the supposed complexity of generics and the very real cost of bugs that will happen from maintaining duplicated code, I'm not sure there's much of a debate to be had.
There is nothing wrong with repeating things. It's important to NOT repeat many things but everything? No way. DRY is a great suggestion but shouldn't be looked at as any type of hard rule.
For instance I've been on projects where DRY was taken to such an extreme that even the function decorators (plus their COMMENTS) were abstracted away because a few words or, at least, a single line could be duplicated. This would require looking through multiple files and figuring out the abstraction code just so I would know where the REST endpoints where.
So I'm not the biggest fan of GO but I see zero reason why it's going to be relearning this specific lesson.
Do we have 67 implementations of sort.Interface? Sure. Is that, by any stretch of the imagination, a significantly difficult part of my job? No.
Juju is a distributed application that supports running across thousands of machines on all the major clouds, on OSes including CentOS, Ubuntu, Windows, and OSX, on architectures including amd64, x86, PPC64EL, s390x... and stores data in a replicated mongoDB and uses RPC over websockets to talk between machines.
The difficult problems are all either intrinsic to the solution space (e.g. supporting different storage back ends for each cloud), or problems we brought on ourselves (what do you mean the unit tests have to spin up a full mongodb instance?).
Generics would not make our codebase significantly better, more maintainable, or easier to understand.
The only distinction is that they're built in. But they are demonstrably useful, and your codebase would be worse without them.
It's hard to argue the hypothetical effect, good or worse, of generics on a codebase. You can't claim to know it for certain, seeing as the definition of generics isn't even completely defined. Maybe it wouldn't be hugely better. I don't think anyone is claiming that generics would be magical.
But well-designed generics (Haskell, Ocaml) can improve in other ways, some more or less subtle than others: You get less boilerplate, more code reuse, expressiveness. Go is pretty easy to read, but I find that the lack of expressiveness tends to obscure the meaning more than it reveals it. For example, you tend to end up with clunky C-style loops where a map would be much more concise, expressive and readable.
Another example: map types. I frequently find myself writing awkward code to copy maps, merge them, apply a common access pattern (such as [1], which coincidentally horrific and probably not performant) and so on. The ergonomics are terrible.
I disagree that it's not a "bother". It probably depend on what the project is; I also work on distributed apps, though.
[1] https://gist.github.com/atombender/01a07926115a17d3a56145adc...
I've been working on a Java project recently, and by contrast, this code base abuses generics to an almost pathological level. I've also been burned by Java's runtime type erasure, and wow that leads to some nasty bugs. (That's not the fault of generics in principle, but Java's poor implementation of them.)
While I appreciate generics in theory, in practice I think Go is better as-is. Too often I've seen generics lead to complexity and abuse which greatly outweigh their utility.
Programmer hubris is a problem. There was a widely acknowledged problem in Smalltalk with the overuse of #doesNotUnderstand: and other esoterica to do "clever" stuff which then makes it difficult for new programmers to debug and understand the system.
There is a reason why certain methodologies emphasize "the simplest thing that could possibly work." As a group, we programmers sometimes waste our own and other's time being too clever by half...an order of magnitude. (Is it any wonder why our estimates are often off by that much?)
Hahaha. This has to be satire right?
>Generics would not make our codebase significantly better, more maintainable, or easier to understand.
Generics are literally a form of abstraction. You might as well be arguing that abstraction doesn't help. Why do you even have subtype polymorphism then? Why not just reimplement everything? That's not a significantly difficult part of your job as you said.
One of the best things about Go is it seems to be a strong signaler of the type of engineering team I avoided.
Is your unstated assumption then that all forms of abstraction must be used? If you've done substantive projects, you'll come to realize that abstractions have a cost, and that everything should be considered on a cost/benefit basis.
You might as well be arguing that abstraction doesn't help.
This is a black and white binary fallacy invoked to then create a straw man, which also seems to suggest that you haven't learned the importance of considering cost/benefit.
One of the best things about Go is it seems to be a strong signaler of the type of engineering team I avoided.
I would agree, this would seem to be a good signaler.
> Hahaha. This has to be satire right?
Nope.
/home/nate/src/github.com/juju/juju$ grep -r ") Less(" . | wc -l
67
(granted, 10 are under the .git directory, so I guess 57)But in any other language, we'd still have the same 57 definitions of how to sort a type.... we'd just have 3 fewer lines of boilerplate for each of those (which live off in the bottom of a file somewhere and will never ever need to change).
> Generics are literally a form of abstraction. You might as well be arguing that abstraction doesn't help.
You missed one word: "significantly". Sure, abstractions help. That wasn't the claim. The claim was that, in a million lines, the lack of that particular way of doing abstractions did not significantly hurt.
Would it have helped? Sure. Would it have helped enough to matter "very much"? No (by NateDad's standards, which may differ from yours).
67 implementations of sort.Interface? Sure, I don't like it, but in a million lines, you've got much bigger things to worry about.
package pleasePutMeInYourVendorsDir
import (
"io"
...
)
...
func sureYouCan() {
// ...
io.EOF = io.ErrShortWrite
// ...
}This started in 2010. Hopefully an illustration that go's developers are not against generics in general, this ought to quell some of the negativity... Pick one of the four proposals you like :)
Before, the default response was "we're thinking about it." Now, it's "let's all talk about it."
Wouldn't the fact that this started 6 years ago and gone nowhere re-enforce the negativity?
I've tried go a few times and it was full with repetition and boilerplate - felt like java 1.0. It seems like new coders like to repeat themselves more often than learn how to use proven concepts.
How do people deal with such things in Go? Do they really make copies of such functions for every type they're working with? (And by type I don't mean just int/string, but all model entities/classes.)
I work on a ~1M LOC codebase in Go and it's really not a problem. map and filter would not make my life significantly easier. They're solving easy problems.
Sure, I have some of this style code:
var names []string
for _, m := range machines {
names = append(names, m.Name)
}
But, really, is that so much worse than this? names = [m.Name for m in machines]
Sure, it's spread across a few more lines, but line returns don't cost extra money... and if you decide you want to later do something more inside the loop for each machine (default the name, cache the ID, etc), you can do that trivially by adding lines inside the loop.This code is not hard code to write. If you're used to just being able to slap out map/filter etc in a single line, I could see how it could be annoying... but it's easy, just write it. There are far more difficult things to figure out in our jobs, why worry about the easy stuff?
It's a death by a thousand cuts. That's 3 dense lines that you need every time you map across a collection.
Which obscures whatever else is happening in the rest of the method/function, so now you've got that much more cognitive load to figure out what it's actual core purpose is.
Which may be subtly modified by the writer purposefully, but you miss it because you assume it's the same "it's just map idiom".
Which may be modified by the writer accidentally, but you miss it because you've trained yourself to parse it as a lump and take its correctness for granted.
You can take that code and interpret that as database query, like C# LINQ.
you frist example is 'how' vs 'what' of second example. Once you stop telling the computer how to do things and just tell it what you want all kinds of things become possible.
Any line of code you don't write, is a bug you didn't write either. I worked on a significantly size go codebase and the number of stupid bugs that popped up, like bad loops instead of a simple `filter` function, were just silly.
Having all this memory safety and other stupid bugs ruled out but not map, filter, or reduce just felt lazy on the Go authors part.
Of course, now that a member of the go team has said it, we'll see a huge shift in what the go team thinks about generics. I'm glad I don't deal with the go community anymore.
I was at one time plugging for parameterized types. Go already has parameterized types; "map" and "chan" are parameterized types. You write "make(chan int)" and "make(map[string] int)". You just can't define new parameterized types; "map" and "chan" are all you get. With parameterized types, you could create more generic data structures; if you needed a generic b-tree or a quadtree library, you could have one. Maps in Go are more special than they should be.
Parameterized types are less powerful than generics, but not too far from what Go now has. The goals in the document mentioned here require generics with all the bells and whistles. Remember, Go still has reflection; if you don't need high performance, you can simulate generics at runtime.
The current interface{} workaround doesn't quite solve the same problem, rather it is like a bad excuse.
To paraphrase Max Plank:
"A new language-level feature does not triumph by convincing its opponents and making them see the light, but rather because its opponents eventually die, and a new generation grows up that is familiar with it."
See e.g. lambda expressions.
Programming is first and foremost a fashion driven occupation nowadays.
That said, he talked about questions of physics, not "truth".
Now, those new theories might or might not be truth.
But the fact that (in his phrasing) they only prevail not because of extra proof, convincing etc., but just because a generation that didn't like them died, doesn't make them seem particularly "truth" based.
Mostly "generational-fashion" based.
It could of course be that the new generation of physicists is also more capable to accept the truth (and Plank might believed that), but this doesn't derive directly from the statement.
The statement only goes as far to say that new generations of physicists are more capable to accept newer theories (the ones that grew with them, and they are more familiar with them than the oldsters are).
Much as I love Haskell, I'm not going to sit here and tell you that a big program compiles quickly.
That might be an individual issue with Haskell, but regardless, isn't type-inference kind of expensive in compilation-land? And wouldn't that kind of kill one of the big features of Go?
Some very strong kind of type inference are undecidable in general and can be very expensive to compute when there is an answer.
But you might bear in mind that almost all static compilers, including Go, already determine the types of arguments to functions so they can complain if you're passing the wrong type. Comparing that against a set of functions isn't much of a stretch, especially if the type matching rules were pretty strict, as they normally are in Go.
Not all generics are generic functions, anyway. A more limited, but still potentially useful, form of generics is generic packages. Generic packages have type parameters, whilst inside the package refer to the specific concrete type the package is being instantiated for.
A hypothetical sort package might have a single parameter denoting the element type it sorts. Conceivably it could be defined as
package sort(T)
func Sort (a []T) ...
imported as import isort "generic/sort" (int)
and then used as isort.Sort.Is binary dependency management just not an option ever?
I have a friend that works for Google and supposedly they have a proprietary build infrastructure that will offload the building of C++ code into a cluster. I sort of wish Google open sourced that as I believe it basically does some form of binary dependency management.
Yes I know Go like C++ can target lots of platform (and thus needs many binaries built) but an organization only needs the major ones. And yes I know the language statically compiles to a single binary but that doesn't mean things can't be precompiled.
Go these days seems to be mainly used for microservices or small utilities and thus you don't need and should not have a gigantic code base for such things. I can understand monolithic UI apps having large code bases but this is clearly not what Go is being used for these days.
There are many other languages that compile to native that seem to compile fairly fast (OCaml and Rust from my experience but I don't have huge code bases).
Is compilation speed really an issue given the use cases for Go?
Yes, I find compilation speed to be one of the most important things. But it is not a selling point for go. The go compiler is not very fast, and speed is not an acceptable excuse for a lack of parametric polymorphism. Ocaml has not just parametric polymorphism, but many other basic type system features. And yet ocamlopt is both 5-10 times faster than the go compiler, and still produces faster binaries.
> Does Bazel require a build cluster? Google's in-house flavor of Bazel does use build clusters, so Bazel does have hooks in the code base to plug in a remote build cache or a remote execution system. The open source Bazel code runs build operations locally. We believe that this is fast enough for most of our users.
EDIT: of course this is good; such a language would be beyond nightmarish. By which i mean c++ or scala.
I like the direction Go is going of "there are no options to choose", like unconfigurable fmt, or the fact that there is no way to create "exotic" implementations.
However, generics would be my number #1 on the list of "maybe let's add that". Would be nice to have less "interface {}" in reusable libraries.
Exceptions would be probably second, but that would come with some sort of runtime cost. With them Go would start to drift towards "generic programming language".
Then go and compare the same code in go and Scala and see which is more "nightmarish". You see Scala as a complex language because you need to learn something before you use it while go is almost the opposite - you may get it in an afternoon and create repetitive, boilerplatish and unmaintanable mess.
While that doesn't make programming in Go the most fun exercise it makes it a profound one (after some getting used to).
Less distractions, less eGo (forgive the pun).
Disclaimer:
I'm still having a hard time embracing all of that myself - I don't even like Go.
I really miss all the functional cleverness I've come to get used to over the years - especially talking Erlang/OTP as the main (losing) "competitor" for most of my backend projects here (microservices, kubernetes yaddayadda).
Weirdly enough I started my career writing selenium test in clojure and got used to (reduce (map (filter ... way of doing things.
Then we moved to python and still I was at least able to do (modified(x) for x in xs where satisfies(x))
Then I needed to do some C# work and I really liked LINQ.
Now I work in javascript and still can at least _.chain(thing).map().filter().value()
It seems that we will use Go for some things, and as far as I know, I am back to using for cycles.
The way I've coped with this so far is to embrace it and just work through the given task - less warm-fuzzy-feeling and more "manual work" and time needed for sure but it wasn't that big a deal once I just let "go"...
Not having list comprehensions definitely is one of those things which makes me feel more like a "stupid coding monkey" but again you can be productive with less elegant tooling as well...
Are there any concrete proposals on the table? I don't recall seeing any, and it would be great to work from that and pick it apart. Otherwise, we're just arguing the opinion that they're useful, against the opinion that they'd soil the language.
I think they would really fit the language well. The good part is:
* Only the package and import statement change, the rest of your code stay the same and is not cluttered
* They are easier to reason about as it is more coarse grained
* They do not break the compatibility
The the bad part is:
* You cannot implement filter/map/reduce (but being able to implement them would conflict with the orthogonality of the language)
* It could lead to code bloat, but not more than manually copy pasting the code.
- Easy to forget to check that pointer != nil
- Overloaded semantics: it's unclear whether a pointer represents an optional type, or is being used to pass-by-refrence (i.e. unclear whether a value should be treated read-only)
- Need to deep copy every struct, which is easy to forget and inefficient (at least the reflect version)
There are solutions to each of these points, but they all add complexity (e.g. generating code), and most take a lot of extra effort. With generics I could have Optional<T>, With a Get() function returning 2 values: the value type, and present (i.e. the way maps work). The caller is forced to handle both returns, making it much harder to forget to check it.
A lot of arguments for generics focus on higher-level functional programming abstractions, but this is a simple and extremely common use-case, and the lack of a solution is responsible for many real-world bugs.
type Optional struct {
stuff interface{}
present bool
}
func (o Optional) Get() (interface{}, bool) {
return o.stuff, o.present
}
func NewOptional(stuff interface{}) {
return Optional(stuff, true)
}> As Russ pointed out, generics are a trade off between programmer time, compilation time, and execution time
This misses the most important metric: quality. Lack of generics forces copying and pasting of code which inevitably lowers quality and increases defects. It's amazing to me that with the all the expense that crappy software causes, we're more focused on compilation and execution time. Last time I checked Golang's performance numbers, the supposed benefits of this focus were not present while the downsides of being a language that forces programmers to do the wrong thing were present as well.
However it is important vastly more important for google than me or you. Simply because google and a lot of other web companies are running on such tight margins.
Consider my company is paying maybe $200/mo for AWS. When we are doing half a million a month in business. Each cpu cycle for us represents a lot more revenue for us than google is making. The flip side is programmer time matters a lot more to you and me than to google.
Edit: My bad. Didn't see the bottom of the page.
But. Go's principle is simplicity and understanding the concepts you're working with. And generics as a concept is a little bit more complex than simple List<int> explanation leads you believe. As C# developer (language with very good generics support), most of other C# developers I've met, unfortunately, can not easily and confidently explain covariance and contravariance concepts in an interview setting — which means that they don't understand generics concept completely. Mix it up with "null" virtual type, and you've got yourself a type system that you think you understand, but really don't, and will discover this misunderstanding in the worst possible moment.
So, while Go sucks for projects that I personally usually work on, its qualities make it a great language for other kinds of projects, and for these projects, generics may not be wort it with a trade off with simplicity.
By Rob Pike, a man of contradictions.
Those times are now way behind us. Today, there is a plethora of languages to choose from, each with their strengths and weaknesses, each most powerful in the niche it's designed for.
Go is not a language with generics. If you need generics, don't use Go.
Go should not have generics unless it's trying to dominate the world. And we all know that no language can achieve world domination nowadays, not anymore. So it should rather trying to be the best language possible in the niche it was designed for. That niche doesn't need generics. On the contrary, a vocal part of the community says generics would taint Go.
Go doesn't have generics. It's however got a proper FFI. Use it. Or don't.
If it just adds a few things, and then when you account for portability and speed, it could be better than the dynamic languages that people often compare it to.
I've read somewhere that it was memory usage related, but i've got trouble picturing why (maybe an example would help)
Having to reach for a complex key combination would be enough to remind everyone that Generics should be used sparingly.