I completely agree with the author, though slightly surprised to see it come from the Go community, which has seemed like one of the more dogmatic languages. (E.g. all the "you don't need generics" arguments over the years.)
The author is implying that most of the "you don't need generics" arguments come from Go's users and not Go's creators.
I don't have a good feel for whether that's true of the Go community, or maybe more importantly whether it's relatively more true of the Go community than, e.g., the Python community.
Arguments like "you don't need generics" or "exceptions lead to convoluted code" or "assertions are only used by lazy programmers" came from Go's creators.
I too am rather surprised to see Go, Lisp and Haskell cited as languages designed by people who are not dogmatic. This may well be true at the micro-level of decisions like "how do you best compile green threads", but at the higher design levels all three languages are very, very opinionated.
I think statement in particular is questionable:
Contrast that kind of discussion with the heated arguments or overly zealous statements you sometimes see from users of the same languages. There's a real disconnect, possibly because the users don't have the experience of weighing the arguments on both sides and don't realize how easily a particular decision might have gone the other way.
What is this implying? The designers of Go and Haskell never make "overly zealous statements" or "heated arguments"? Isn't the history of Go littered with such arguments, the most famous being that Go is simple because it's designed for Google engineers, who can't handle complex languages?
It's definitely true that language communities are often zealous: when library interop is bad they have to be, in order to get an ecosystem of libraries and tools. That's why you see people posting things like "I made a terminal emulator in Rust" to Hacker News but don't see people posting the same for C++ or Java. Those languages have established ecosystems already and need much less evangelism.
Don't know about Go, but the one thing you don't see in the Haskell community is zealotry. Being opinionated is not the same as being dogmatic.
Haskell developers have a clear position of letting features get in the language as the community pushes for, and the overall guidance is very much open to changes. The one thing you won't hear there is any equivalent of "generics are bad for you, we are not adding them".
Strong opinions are not dogma as long as they are open to understanding the other options and change (as here).
I think he's just trying to say that people should be less tribal and dogmatic about their tools, ,and users should try to understand that language design is about trade offs, not holy war. I think it is unhelpful to lump rsc in with rob pike, they are very different personalities.
For a year or so, I used to make minor contributions to the Go programming language codebase, back before it hit 1.0. That certainly by no means makes me an expert on it, nor do I claim to be someone of the caliber of Russ Cox or Rob Pike, but I did do a lot of reading on golang-dev and golang-nuts, before the language was settled on 1.0.
There were a lot of discussions on generics, and the language authors and contributors were certainly open to ideas and indeed did their own fair share of research. For example, see https://research.swtch.com/generic from 2009. Indeed, the FAQ entry itself on Generics states 'Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do.', and looking at the blame log, that's from 2010 or earlier, and it's stuck to this day.
So, from everything I can see, the creators themselves are certainly open to the concept - I don't see any "you don't need generics" coming from that side.
If you follow the history of that particular issue, you'll see that the Go team's (official) view has always been that there are tradeoffs involved, and that none of the existing proposals for implementing generics are sufficient improvements over the status quo. Generics are not free, they bring a lot of baggage with them. And I think most people will agree that Go has achieved a lot of success without them, so perhaps most use cases don't require generics after all.
>Only everybody who asks for generics knows that, and still wants them to go through and pay the price.
And those people are outnumbered by people who are comfortable and productive with the current Go implementation.
Programming languages are not all placed on a single Bad<---->Good scale. There are many variables involved, and it makes sense to have local maxima like Java, Go, Clojure, C, Haskell, etc.
>And those people are outnumbered by people who are comfortable and productive with the current Go implementation.
The only way to know that would be to be able to compare one implementation with and one without generics. But only one Go implementation exists. Perhaps there's some toy attempt by someone, but not anything that is equal in all other aspects to mainland Go but with Generics on top.
Plus, since most of the burden falls on the compiler writers and most of the benefits to end users, it just takes the compiler writers to be "comfortable with the current Go implementation" for this to continue to remain Generic-less.
Here's a question for you: what if the Go team add user-defined generic types to the language, and people start demanding HKTs? What then? Do you implement them and hope nobody asks for dependent types? Where do you draw the line?
Well, it's a soristic problem, but there are some clear heuristics:
1) tons of people have asked for Generics, few have asked for HKTs.
2) C++, C# and Java, programming languages with millions of users and extremely battle tested have Generics, no mainstream language at that level offers HKTs.
3) As a result of (2) a ton more people are familiar with Generics than HKTs, and so the latter are much less probable to be requested.
And of course, if they do add Generics, and people ask for HKTs, they should consider what they do about that then and there, not now.
No reason to never make a step towards somewhere just because you might (or will) be asked to also take a further one.
Except if they believe that they are already at some optimal point (which is my case I think).
What about all those people who came to Go from Python, JavaScript, Ruby and whatnot? They are mostly content with the current status quo, generics would just make the learning cliff steeper for them. And according to the Go devs, more people come to Go from Python than from, say, C++. So perhaps it makes sense to stay at that level, at least for now?
All I'm saying is this doesn't seems like a simple equation to me. There are many possible solutions to the problem of building large, correct, maintainable, performant programs - and neither of the existing ones looks like a surefire winner given all the limitations.
This is probably a self-fulfilling cycle to some extent... the people using go are by definition willing to tolerate the lack of generics, while those for whom it's a dealbreaker won't be represented. If go did introduce generics, it might start appealing to other types of developers.
Personally, I love most things about go, but its utter clumsiness at iteration and data transformation relegate it to a fairly limited number of use cases in my mind.
I agree containers are the one area this is a painful lack at present. Perhaps they'll look again at this in a Go 2 if they ever get round to that.
I do also really value the lack of change in the language though, it is important and rare that it is stable and not accreting features every year (compare with rust or c++).
People who came to Go from Python, JavaScript, and Ruby will likely have had a steepish learning curve getting used to pointers in Go. I hadn't used pointers since programming in C++ in 1995 when I switched to Go, and it took a bit of time to get used to remembering that extra detail.
In fact, because I used Clojure just before switching to Go, I found not having immutable variables harder to get into the habit of more than anything else. I actually first used Go because I needed to write something that manipulated bits in memory reliably, and couldn't be bothered going back to Java.
The Slippery Slope is also a fallacy, and this exemplifies why.
Java didn't have generics. Then they didn't have lambdas. Now they're even redoing the classpath! Have they gone nuts?
No, they've been evolving, and they've kept on adding features as it was opportune and useful to do so. If tomorrow's programming paradigms depend on HKTs and dependent types, then, yes, Go might want to add them. Today's programmers are just about starting to understand what a dependent type even is, so you're suggesting to include them in Go in order to ridicule the potential inclusion of generics. This is a fallacy, because generics and dependent types are years, decades, apart. And generics are very much part of how several generations of programmers have learned, and expect to be able to program.
If your language stops changing, your language is already dead. Yet another point of similarity between natural and programming languages.
I didn't mean to ridicule anything. My point is that there are multiple evolution vectors for programming languages, and generics->HKT->DT->? is just one of them. It's quite possible that in the foreseeable future the biggest winnings might come from tooling/libraries/ecosystem rather than the type system. (Of course, those are mostly orthogonal.)
The argument works against any language feature, not just generics.
As a aside, why not add HKTs straight with generics? The problem the Go implementors have with generics seem to be about run-time representation (boxing or unboxed), but no additional run-time issues arise from HKTs.
The main issues of HKTs are that type inference becomes harder. Haskell's approach of eschewing type-inference at the kind level (because it's undeciable anyway) but giving unkinded type variables the kind * seems to be a run-away success.
Irrelevant. Go already has parametric functions like append, it just doesn't allow user defined ones. append isn't enough when a programmer needs to remembers 10+ tricks to make up for the lack of generics :
> And those people are outnumbered by people who are comfortable and productive with the current Go implementation.
Citation needed. How do we know that the majority of Go users would prefer not to have generics? How do we know the number of non-Go users would be willing to use Go if it had generics is insignificant? Is there some large scale survey of Go users somewhere you can cite?
How is that comment anything but a refusal to move on unless some magic tradeoff free solution appears? It even ends saying that "Programming language researchers are sometimes disappointed that Go hasn’t picked up more of the recent ideas from the literature, but those ideas simply haven’t had time to pass through the filter of practical experience. I believe generics is one of those ideas.".
But Generics are neither some new PL topic, nor have they not passed the "filter of practical experience" (millions use them in C++, Java and C# alone).
I'd rather they explicitly said: "It will take a total breakthrough in computer science regarding generics implementations for us to consider them for Go, and we wont ever care of paying the costs of any current, viable in practice for millions of programmers in other mainstream languages, approach".
> How is that comment anything but a refusal to move on unless some magic tradeoff free solution appears?
Well I (somewhat) agree with that, but my take of it was Russ is basically saying existing approaches "don't work well with Go" and "recent ideas" are "not well understood" with the concern that "there's a princess in another castle after that one I am sure."
Or put it another way, per his emphasis on the "engineering" nature of Go, it is not an issue of tradeoffs, rather a concern that a runaway train of complexity will follow. Or if you will excuse my Mexican French, the problem is the fucken type system :)
> How is that comment anything but a refusal to move on unless some magic tradeoff free solution appears?
A programming language design is the combination of a set of features. The hard part in designing a language is working out how all these features interact with each other.
For example, one feature of Go is that every type has a default value (0 for decimals, nil for pointers, empty string for strings, etc.). This property leads to the inability to include algebraic data types cleanly. To see this, consider this simple ADT (in Haskell syntax):
data Either a b = Left a | Right b
What's the default type here? It could be "Left(default-value-of-a)" or "Right(default-value-of-b)". You could introduce additional syntax to define the default value, but that increases complexity. You could introduce a rule how the default value is inferred in these cases, but that goes against the Rule of Least Surprise. You could remove the "default value for every type" feature that clashes with ADTs, but that causes problems elsewhere. Therefore Golang lacks ADTs and uses pointers and multiple return values instead.
This is a dangerous and slippery slope to engage on.
Java was also extremely successful in 2004, before it supported generics. Yet you'd be hard pressed to find a Java developer who thinks that generics were not overwhelmingly beneficial to the language, and probably one of the main reasons why Java is even more dominant and powerful today than it was pre-generics.
For my purpose generics in Java are not overwhelmingly useful. I work on a quite big and successful, Java application inside our company. And it was written mostly after Java 1.5 but hardly use much of generics. It may have many issues but none related to generic.
I am not saying generic are not useful. But I have not found enormous use of Generics in typical Java business application. The most common generic data structure I have used in Java are Maps and lists which are already generic in Go.
I think you are underestimating the indirect effect that generics have had on your code, even if you don't use them directly yourself.
Thanks to generics, millions of lines of code in libraries that you are using became safer and faster, and you are benefiting from this, even if it's not apparent to you.
Basically, generics made Java a better and safer language. Both theoretically and practically.
I have been a Java developer since the JDK alpha in 1995. It was apparent then that Java was going to be pretty big due to the fact it was VM-based, addressed a lot of pain points like platform independence/concurrency/interfaces/etc., and was similar enough to C/C++ that people would adapt to it quickly. The language experienced an enormous network effect due to the tooling and support libraries that allowed Java to become dominant in enterprise development.
It's hard to argue that a feature that appeared a decade later changed the equation all that much. I like generics but would still use Java even if they did not exist. In my work the concurrency features are far more important.
Buyin and successful are not necessarily the same thing.
Designers of Go have been disciplined enough to keep things simple - let's you keep more of the problem domain you're trying to solve, along with the code to solve it, in your head. Try that with a big project using another toolchain - lots of frameworks, lots of places to plug your code, with less coherency (in my experience) across the various experience levels of your team. And I'm talking about big teams here, not 10 devs.
Subjective Success metric with comparable teams: Go vs <x with generics>. If Go team gets solution done faster, correct, within budget compared to other team, your stakeholders will not give a crap about what devs think about generics. There's a good chance that would happen, based on my experience.
Types like Object and Comparable<T> allow heterogeneous collections and algorithms, which are occasionally useful even in an otherwise statically-typed language. Think of it as a set of types where otherwise you would have required boxing everything within a single type and eventually reimplementing the type system.
If Java had generics from day one, they could have put methods like equals and hashCode in interfaces that some classes don't implement, instead of passing static type checks and then blowing up at runtime because you did something that never made sense.
Well, except that some of us do remember Java 4 fondly. It was a much simpler language and that was quite a good thing. I'd say "overwhelmingly beneficial" is an exaggeration.
Modern languages like Rust and D are being designed with generics and people are supposedly paying some prime for that complexity. I wonder if anyone using generics in their language would give them up for some increase in performance or language simplicity. It's doubtful.
Internally, the Go team has drawn up proposals for adding generics [1] but rejected them because of drawbacks. Hopefully one day we can see those drawbacks, because maybe a lot of us are more than happy to pay that price.
One of the main issues with Go is that we can't see those discussions. With C++, C, Rust, D, Java those proposals are public from Start to Finish.
From the discussions I've seen on what Go considers complicated I'd bet it work out to be something like name mangling.
Go turns off ASLR for loaded C code because its easier to debug. Which is really only true if your debugging process requires memorizing the absolute address of symbols that control flow will jump too. I guess that is useful if you are reading the raw hex streams.
Name Mangling is complicating things because you can no-longer debug with ElfRead.
Go seems to reject anything that was invented after the ~70's. As those concepts are complicated.
I can't help but feel that Thompson and Pike just don't want to change the bad habits they've developed from by-gone ages and adopt modern tooling.
Rust without generics would not work well. (FWIW, I consider Go quite usable and enjoy using it.) The reason is that Rust very much depends on building up safe abstractions around data structures with unsafe implementations. Without generics, folks would need to re-implement those data structures and `unsafe` would become profligate, which diminishes Rust's core value proposition.
> If you follow the history of that particular issue, you'll see that the Go team's (official) view has always been that there are tradeoffs involved, and that none of the existing proposals for implementing generics are sufficient improvements over the status quo
They should have thought about it at the very beginning of Go design. it's easy to say now "We didn't find a good way to implement generics" when their design choices made implementing generics insanely hard at first place.
Well actually,they thought about it, where does append,delete,make and co come from? how do these functions know their arguments and return types at compile time? generics.
Furthermore it's not all or nothing. Go could have supported parametric functions in user land just like append,make or delete without implementing full-on generics. They just don't want to bother with that, because it's impossible to retrofit generics now without breaking the reflect package. The rest is just excuses to evade the issue.
> Generics are not free
Creating a modern statically typed language WITHOUT generics isn't free either. Just like implicit interfaces are not free, just like the reflect package is not free, just like using interface{} somewhere isn't free, just like telling people to use code generators isn't free.
Your argument doesn't explain why a programmer cannot implement his own append or delete functions on existing container types. These exceptions do not make sense.
The argument is more along the lines of "the upsides of Generics are known, and the downsides are that many of our users will find them confusing". That pretty much says what you need to know about the language.
I wonder what the point of railing against a lack of generics is. When I sit down to write Go, I know that I will be writing a lot of simple, yet repetitive code. It's part of the bargain.
Still, if you really feel passionately that Go with generics would be frickin' awesome, generics seem like a pretty easy thing to write a preprocessor for. With the great knowledge of programming language design and implementation that most pro-go-generics internet forum posters have, it should be a piece of cake.
I don't think it's very easy, particularly the error handling paths. Further, `go build` and friends don't support preprocessors. Much ink has been spilled about the difficulties of implementing generics.
Ply is like Go except you can write things like []int{1,2,3}.filter(even) and it will Just Werk.
It's not quite ready yet, but it feels promising. The idea is that Go doesn't really need full support for generics; most people just want a few more higher-order functions available for manipulating slices, maps, and channels.
Have looks pretty cool, and their project is certainly far more involved than mine (e.g. they have their own lexer/parser). I am excited to see more compile-to-Go languages. The only other one I'm aware of is iGo.
That said, I'm curious to know if Have is able to eliminate intermediate structures (deforesting) when chaining together transformations. Haskell performs "stream fusion" to accomplish this, but I'm not aware of how it's done in other languages. Ply does support this optimization, and that's mostly possible because it restricts the generics to a built-in set (rather than allowing the programmer to define their own generic functions).
My other goal with Ply is to make it as familiar as possible to Go programmers. It's not so much inventing a new language as it is scratching an itch.
> I wonder what the point of railing against a lack of generics is. When I sit down to write Go, I know that I will be writing a lot of simple, yet repetitive code.
Your second sentence answers your first. Not everyone likes writing the same code over and over again, even if it's simple code.
> generics seem like a pretty easy thing to write a preprocessor for
Very funny. But even if it were possible, the bigger problem with using a preprocessor is that everyone ends up using their own incompatible generics preprocessor.
The best possible outcome is that everyone agrees on the same preprocessor/macro-expander and we start treating Go as a compilation target that isn't suited for human beings to read or write.
Others have pointed out this is how we got C++ and stopped putting method dispatch and stack unwinding boilerplate at the same level as the code we really care about.
C has macros handled by C compilers, no need to hack templates with third party pre-processors like Go gen. You can't even compare C and Go in that case. In fact C is more expressive and type safe than Go.
C macros are not hygienic, which is one motivation for C++ templates. I agree with what you're saying about the pre-processor being part of the standard but it's really hard to write those things in a way that is safe.
There were many motivations for C++ -- and many more seemed to get added as it went along -- it's not just generics parametric polymorphism. Of course I would hate to say: Go should have every language feature or else we'll end up with Go++ or Objective-Go or something like that. But simplicity is not just a matter of not providing for something.
In my experience, the defenses for Go's stance on generics seem to be more nuanced than the criticisms. The Go community seems to agree that generics would be useful at least in some situations, but that many of the criticisms are exaggerated. On the other hand, many of the criticisms of Go amount to "Go is unusable because it lacks generics". YMMV.
> The Go community seems to agree that generics would be useful at least in some situations, but that many of the criticisms are exaggerated
The general consensus from the Go developers is probably best summarized as "Generics are useful, but each of the implementation methods we have seen so far have significant tradeoffs, and the benefits are outweighed by the costs of the methods we've seen."
Which isn't to say that Go will never see generics, if someone finds the right way to implement them.
> "Generics are useful, but each of the implementation methods we have seen so far have significant tradeoffs, and the benefits are outweighed by the costs of the methods we've seen."
I think what people tend to disagree with is the last part. There is a lot of leeway in estimating the costs and benefits and people end up with very different estimates.
I am not a Go user, but I've been following it from the beginning. What I've observed is that since one of its design principles is simplicity, it left out features that many people think are essential for a modern programming language. This results in a lot of criticism of the language which provokes a defensive reaction that might overstate their position.
The truth is that one does not need generics. I've worked for decades in languages that don't have generics. The real question is with current software capabilities and requirements, is the convenience provided by adding generics to Go worth the cost of the added complexity. People clearly disagree, and the language designers decided "no". That's not dogma; that's engineering.
Sure ,when you can use interface {} AKA void pointers everywhere, you certainly don't need generics /s
You need generics if you want to avoid context.Context.Value(interface {})interface {} like API. You need generics if your goal is type safety AT COMPILE TIME. Obviously it's not one of the goals of Go /s ...
Here is an illuminating (to me) post that shows how the Go approach to solving algorithmic problems might be different from other languages (in this case, a generic graph data type + Dijkstra's algorithm):
Out of curiosity, how would you avoid `Context.Value(interface{}) interface{}` with generics?
The net/context package was designed to pass request lifetime context throughout your service infrastructure (think microservices), where the values provide metadata from your transport protocol. HTTP headers, if we want to think in terms of HTTP, although obviously there is no specific protocol dependency at this layer. The value may not even be assigned by a Go program, so it seems that runtime validation would be a necessity even with generics. A broken or malicious program upstream could easily inject something into the context that you weren't expecting, or simply not supply the value at all.
To clarify, Contexts don't automatically get HTTP headers stuffed into them, right? Instead you mean that some layer of the Go program would insert data based on headers, and a later layer would take it back out? I'm not a Go user but that's what I gathered from some Googling.
So, if you want to use types to make Context safer to use - and I'm not saying this is necessarily desirable, but assuming it is - you could start with something that works in the existing Go language: explicitly marking what kinds of objects can be keys rather than using interface{}.
type Key interface { _dummy_key_method() }
type Context interface {
...
func Value(key Key) interface{}
}
func WithValue(parent Context, key Key, val interface{}) Context
(Incidentally, I find it odd that WithValue is a freestanding function while Value is a method; I suppose this is to reduce the burden of reimplementing the Context interface for a new type, but is there any actual reason to want to do that?)
This helps prevent you from accidentally using the wrong object as a key, a bug that might be tricky to catch if the key you meant to use is legitimately sometimes not present in the Context (in which case your code wouldn't error out due to nonpresence of the wrong key). It also prevents hacks like using strings as keys in lieu of declaring dedicated constants, which would create the risk of silent failure due to typos. (It seems this is already considered a bad practice, but such a signature would enforce it.)
Once you do that, it's apparent how generics could further improve safety. Using imaginary Go syntax:
type Key<T> interface { _dummy_key_method() }
type Context interface {
...
Value<T>(key Key<T>) *T
}
func WithValue<T>(parent Context, key Key<T>, val *T) Context
Now you're safe from type mismatches. Actually, it seems that best practice is already for packages to provide type safety by defining wrapper functions, such as this from userip:
That works well enough, but generics save you the boilerplate; packages could just expose the keys as public constants while retaining type safety. Admittedly, those two functions are only a few lines to define, so there's not that much boilerplate to save, but it's something. Generics would also make it possible to write type-safe generic helper functions involving contexts, though tbh I can't think of many obvious ones you'd want. Maybe a function that panics if it can't find the key:
func MustHaveValue<T>(ctx Context, key Key<T>) T
...though that would be better handled by a proper Option<T> type anyway. But that too would be enabled by generics!
Okay, but there's still more that the compiler could guarantee. The value is now guaranteed to have the right type if it's there, but for all we know it could be missing. Indeed, this is probably a bigger footgun than types are in practice: it's easy to accidentally have some code path that bypasses the code responsible for setting a particular key. Unfortunately, this is a harder problem to solve even with generics, and in fact multiple Rust web frameworks have run into very similar issues:
You rarely have `interface{}`, you usually have concrete interfaces. These have the same amount of type safety at compile time for function inputs, there's just an additional runtime cost, which the Go folks are willing to pay.
The fact that interface code often has runtime failures is usually due to a feature Go interfaces get you that many languages with generics don't have in the first place -- the ability to downcast to concrete types.
Now, interfaces sadly do not have the same amount of type safety at compile time for defining new containers and you can't do things like return value polymorphism as well, but it's not that bad.
It requires a different style of programming (the post from rsc linked in the sibling comment is particularly good at illustrating this), but it's not that bad.
I say this as a Rust programmer (also a Go programmer, but primarily a Rust programmer) who absolutely loves generics.
I was using a more generic "you", talking about most of the Go programs I've seen. By "you" I meant "Go programmers". Your situation may be different, but I'm describing what most Go programs (that I've seen) look like wrt their use of interface.
I write a lot of algorithms that can work with float, double, complex float, and complex double. Without generics/templates (or a macro pre-processor), I have to cut and paste that code 4 times, or I have to sacrifice performance. Maybe I don't need templates, but I really really want them.
I thought the same thing. Try telling the go community that style consistency is overrated and that "go fmt" has some really bad styles in it. As far as I can tell, in that direction, only dogma lies.
I believe this is less dogma than an engineering choice that some conventions are necessary to enable readability, avoid obvious errors (like forgetting braces around conditional logic), and make merges as easy as possible. Within bounds it does not matter what those conventions are so long as everyone uses them. Over time I have come to think this is the only reasonable way to approach coding conventions.
Just to be clear, I personally dislike tabs and line-lengths over 80 characters. However, such choices have no bearing on the success or failure of any project I have ever experienced.
My point is that it's dogmatic for the entire community to use a single style. I've worked on countless different C programs over the years, most with their own unique style, and while I think it's good for a project to use a consistent style, using it across every project seems dumb.
Cross-project style consistency doesn't buy us anything reasonable--there's no "engineering" about it.
Also, braces around conditional logic isn't what I'm talking about here (since that's not a "go fmt" style choice--that's the language syntax).
This is likely to be controversial but code conventions are like GOTO statements for me--it seems best to adopt baked-in formatting conventions so that it's no longer possible to waste time and energy arguing about them. Also, it avoids the exertion of chasing people who don't do it consistently and mess up merges or ability to see diffs. Finally, it eliminates the bad feelings that result from religious arguments about code formatting.
Most project teams have limited resources to come up with a solution that is close enough to correct to make the project pay off. In new technology there also tends to be a small set of technical issues that are hard and that you must get right--nothing else matters that much if you fail on the core. I am pretty heartless about anticipating things like code conventions, security requirements, legal review, etc. and just following the rules or otherwise making them non-issues for the project team in areas where it does not fundamentally alter the outcome.
The Go core team has been opinionated but not really dogmatic.
Their argument has never been that you don't need generics. It has been we don't want to add them in the currently available forms right now and here are the ways you can get around their absence in the meantime.
I think the reason you have those kinds of holy wars among users of a particular technology, whether it's an OS or a gaming console or a programming language, is network effects.
All of those things are more valuable the more people that use them, and if you've invested money or time into any of them, you have more of a pay off on your investment if more people also use them. So you might feel strongly incentivized to defend your platform of choice while attacking competing platforms, even if another platform might be marginally better, but not enough to justify the switching costs.
If you're a language developer, you have dual incentives-- one is to get people to use your language, which would seem to encourage you to engage in holy wars, but the other is to be good at developing languages which requires you to think objectively about what you and other language developers are doing, so even if you're publicly defending your choices, you should be open to the idea that another way might be better.
I doubt the vast majority of this behavior is at such a strategic or even conscious level. People are just inherently very tribal, and prone to retroactively rationalizing their choices. That's true in every domain, including programming tools.
Being tribal just aligns well with the platform incentive: the more people use it, the better is return on investment for every participant. The participants are not required to be conscious about this; the evolution will favor them anyway if they are really numerous.
> I think the reason you have those kinds of holy wars among users of a particular technology...is network effects.
I find the holy wars are usually fueled by "fans" -- people less invested and less knowledgable in the topic. This is true of movies, bands, sports teams, phones, computers and cars.
The fact is if you work on the tech you appreciate that there are tradeoffs and know what your overall objective is (so are less likely to argue about things that don't get you there). Musicians tend to get along -- did the Beatles hate the Stones (OK, the Stones released "Let it Bleed"). I doubt Mark Hamill hates Star Trek or Shatner Star Wars. The catholic pope meets with the grand mufti of Jarusalem. Obama and Bush talked a lot in 2008 and 2009.
It's like the joke about academia being so cutthroat as the stakes are so small. Holy / Flame wars erupt among those on the periphery at best.
> I find the holy wars are usually fueled by "fans" -- people less invested and less knowledgable in the topic.
There seems, IME, to be a sizable contingent of people who have invested heavily in a particular technology and are threatened, in their own market value, by threats to its distribution, in most technology flamewars. They aren't necessarily less knowledgeable, but frequently prefer depth in a platform to breadth across platforms for developing their own skill set, which creates a strong incentive to promote the platform.
It took me a long time to understand the 'stakes are so small' statement about academia.
I used to see it as a humorous statement about the disconnected irrelevance of academia.
As a professional academic, I now read it as 'the stakes are so few'. Academic politics are rigidly contained by existing structures and limited funding. There may only be one or two ways to 'win' within the framework, with a limited number of 'winners'. In such an environment, vicious politics may be unsurprising.
Reminds me of what a touring musician friend of mine said about the Austin trad music scene. "It's cute how they bicker so much. You'd think real money was involved!"
I think there's more to it than that. Languages, including programming languages, affect the way you think. When you're proficient in a language your brain adjusts to the language's styles and limitations so you can express yourself efficiently using those modes of thought. If you are only proficient in one language you can fall into the trap of thinking it is the only way of expressing your thoughts efficiently, and that's where you get the heated flame wars. This is why it is important to become proficient in languages with completely different paradigms to better understand the trade-offs. When I switch between two languages in which I am proficient I can almost feel my brain shifting gears to a different mode of thought.
I find it a bit funny that the Sapir-Whorf hypothesis, though invented for and now largely discredited for natural languages, turns out to strongly apply to programming languages.
Wikipedia says "Currently, a balanced view of linguistic relativity is espoused by most linguists", so I wouldn't call it discredited, just more nuanced.
People have used the term linguistic relativity to mean anything on a vast range of specific hypotheses, which makes it hard to figure out what exactly people mean when they say it's been proven or disproven. Nevertheless, it's pretty clear to most people that the stronger forms of the hypothesis--the idea that language is essentially a straightjacket on the kinds of thoughts you can express (or linguistic determination in general)--is thoroughly debunked.
I personally rather suspect that the weaker forms (that language generally shapes but not fully limits how you view the world) are also equally fallacious, even if language and world-views might be weakly correlative--instead, I see language and world-view as essentially both being strongly derived from that of those who teach you.
For example Mathematics is generally accepted to be incomplete it what it can express (Gödel), and programming languages, while expressive and in theory turing complete, still limit the thoughts you can easily express, which I suspect will guide users to do things a certain way.
Natural language is a bit different because of borrowed words and the flexibility of the humans parsing it.
There's also this, from a similar milieu:
"good notation has a subtlety and suggestiveness about it which at times makes it seem almost like a live teacher . . . and a perfect notation would be a substitute for thought." Bertrand Russell.
("diagram chasing" in category theory might be an example of thought being almost eliminated by a notation...)
a perfect notation would be a substitute for thought." -- Bertrand Russell.
Something that would only be said by someone who never thought about the time and space complexity of theorem proving algorithms. (Given his areas of contribution, I can't be sure if he ever did or he didn't.)
He also said "Most people would rather die than think; many do."
In other words, not all human cognition is "thought", for Russell. So I don't think he's making any claim that implies total absence of computation with a perfect notation. I'd say he's in the clear on this one.
> Languages, including programming languages, affect the way you think
That's a nice-sounding hypothesis, but it's difficult to falsify. Steven Pinker reviewed some of the scientific literature on that idea in "The Stuff of Thought" and it mostly holds up the null hypothesis.
I believe that hypothesis is true in the trivial sense -- nearly everything you learn affects the way you think -- but I'm not convinced it's true in the more interesting sense.
>(for example both Go and Haskell need some kind of “green threads”, so there are more shared runtime challenges than you might expect)
That's a very generous definition of similarities, as tons of languages have green threads, even Java/JVM had them initially (and still has them in various implementations over native threads). Could even add that most languages also have GC, some type system, io, modules, etc.
Name a language having a compiler that most similar to Go in the threading domain, e.g.:
compiles to machine code
have green threads
use non-blocking IO in the background
have parallel runtime
Haskell is the first that comes to my mind. (I guess that Common Lisp is a safe bet too, as it had everything for ages before it was cool;), but I'm not familiar with that domain to be sure)
Erlang have everything except machine code compilation.
What else? I guess the rest is from the newer, lesser known or experimental languages. So is there anything more popular than Haskell?
Occam (!= Ocaml) did CSP akin to Go AND static compiling back in the eighties. Various schemes have had both too (Racket, Chicken, etc), and CommonLisp is indeed a safe bet...
I majored in systems and distributed programming, and had the luck of having a set of teachers that cared we should know the history of computing, making us curious about our past and how those designs could influence current ones.
So the habit of hunting and reading papers stayed with me, even if during the day I just do boring enterprise consulting.
Many times they are more entertaining than reading newspapers on the daily commute.
The problem is when we form Groups or Tribes based around something we fall into the trap of just whitelisting or blacklisting things. This is a natural human algorithm to simplify things in our brain and make our decision processes more efficient.
A way to fight this is to make sure you belong to more than one language community. Then you'll be forced to not make such a simplified model.
"Go" is such a poor name, both for a language and game, because they collide with the English word, it's impossible to perform targeted search or sometimes even to know what an article is about.
Some TV shows like "24" suffered from the same fate. On reddit the game of Go is represented by the Korean "baduk", probably to escape the confusion cone.
Agreed, but that is a technical problem that can (and should) be solved. For example, "C" and "C++" are even more unfriendly terms for search engines, yet Google handles them just fine:
When you feed them unambiguous queries like that, yes. "stdlib" is a very unique word specific to the domain of programming. Things get trickier with terms that have more overlap, and irrelevant results tend to creep further up the page.
It's rather strange and ironic, given that Go came from Google, that they used such a search-hostile word.
("Golang" is a lot less problematic, of course. Hell, in an alternate universe, why not "Gol"? Only one more letter, and no serious collisions I can see...)
You're always going to get collisions, the serious ones are with other technical topics/brands/etc. No one is going to mix up a programming language and an airline.
Unless you're writing in Perl or javascript, almost every programming that exists is either a short English word, a person's name, or just a letter with maybe some punctuation.
The -lang suffix is widely used in the PL space (Golang, Nim-lang, Crystal-lang, etc). Programmers are required to be proficient with computers, therefore it is expected of the target audience to understand how to work with a search engine when a word is ambiguous.
There's also the fact that (at least) Google uses your private information, global search trends and whatever else to try to make a better guess of what you mean. In my case, just writing "Go" gives me back golang.org as the first result.
But this is not how languages are designed and even how Go was designed, where three people had to agree on features, instead of making "engineering trade offs". And this was a decent decision for familiarity and making the language more approachable, even though it was all dogma. The reason is simple: languages follow the same rules as user interfaces in general and as in user interfaces first thing that comes to mind is usually a better choice, than something overthought by a non-professional in UX, because you have it engraved in your brain by your most often previous experience and therefore it's very likely that many people do too. And having multiple people to agree basically supercharges that.
But then you still have to follow the rules of the UX, consistency, flexibility, all that, and none of the languages I know of are serious about that. It's all just dogma everywhere, or "engineering trade offs" if you like. We don't have much dogma only when it comes to performance, since it's the only thing easily measurable.
Regardless of the community, we generally all strive for the most efficient way to accomplish a goal, but the definition of "efficient" can vary: execution performance, elegance, ease of developing, ease of integrating into X... Often, one or a few leading solutions emerge but it's tempting to take those pragmatic optimums and turn them into law—or worse: follow them for the wrong reasons.
It's a good idea to know why you're using a particular design tradeoff, and remain flexible to change if you find your constraints have changed. Some environments make this interchangeability easier than others but there's almost always a choice.
Language designers are indeed often un-dogmatic about the language. The tool builders in Go however have built a very structured distinct practice of using Go that forces one to remain in dogma. Go is rather unique in how much Go-tooling-specific tooling knowledge one has to have to even begin writing code, owing to it's deeply inbuilt sense of workspaces (GOPATHs) and it's strict directory structure.
I really think this is a great post, and emphasizes a strong danger in adopter culture. But I do also think Go, in particular, is rigorously dogmatic in it's tooling. To good effect, in that anyone can come to any project and understand some basics, but the workspace decision is really hard to reconcile with the project-by-project view of the world that most language tooling assumes.
There are a lot of decisions in Go that I find very distasteful. It's frustrating because Go comes very close to being something I'd want to use, but falls short in all areas.
It has this great automatic interface system! Amazing! Except it also has "primitive" types that can't participate, so many of my functions have to take interface{} instead which is pretty much void*.
It can dispatch to different functions based on the argument types! It will even automatically switch to the non-pointer type if you pass a pointer to a non-pointer function. However, you can't write a pointer and non-pointer version of the same function. Also why are we using pointers to represent reference mutability and optionality these days? Other languages have maybe/option which handles optionality much better.
Speaking of not having maybe/option, every other line of code is "if (err == nil)" and there's no way to abstract this out. Give me `option` dammit. Exception handling exists via panic, but it's a little different than you're used to and the language designers like to pretend it's not exception handling at all.
There are built-in functions that use generics, but I'm not allowed to write one? Hypocrites.
You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever. It also gets extremely annoying when you want to serialize json, serialize a protocol buffer, or do a database query. Name your struct members in lowercase and those libraries won't be able to see them, but name them in uppercase and the output will be in the wrong case. Instead, you have to "annotate" those struct fields repeatedly for every library you're going to pass them to. You also have to manage "naming convention" systems that translate between the two names, and these conventions are never the same between two libraries.
I wouldn't say Go has structural typing. It has a form of interfaces, which are not the same.
Structural typing is when the data members of an object are expressed in its type. For example, `x : {f : t}` is a statement that the object `x` has a member called `f` of type `t`. That is structural typing.
On the other hand, what Go has is pretty much the definition of interfaces, where you have constraint types that indicate that anything satisfying the constraint has some functions defined on it.
Ah, ok. I'll change my comment. I've heard it referred to as structural typing though, and googling "structural typing vs duck typing" gets the right results.
This isn't the same concept as structural typing I don't think, but purescript has something called extensible records that quite similar to what the previous poster describes:
function :: forall a. { f :: String, g :: String | a } -> String
This is a function that takes an extensible record. i.e. 'a' is some type which has fields f of type String and g of type String, at minimum.
Extensible records are a form of structural typing. Purescript has good support for both nominal (via data declarations) and structural (via extensible records) typing. It has the best native support for both paradigms of any language that I can think of off the top of my head.
I remember this biting me when I wanted to write a function that would interpret the value I passed to it, and have a special case for null pointers. I wanted to also be able to pass a non-pointer to this same function name and get the standard case, but the dispatch would not let me do this unless I gave both versions of the function different names.
The gotcha is usually when you're trying to use that type to satisfy an interface. If the methods that satisfy an interface are defined on a pointer, then you need to pass a pointer and not a value.
I must say I really like it. The only point where it can be very annoying is for serialization. For all other situations, I think it's much cleaner and faster to read than always adding keywords.
Sorry, it's nil, isn't it. It's been a year since I touched Go. I wrote enough to get a production component out using it. I mostly used it to fill in the gaps where Python wasn't all that good at being a reactor. 2k lines tops.
I didn't want to nick-pick here, OP could've just said that.
It's the same as not having a ternary operator. Go is no language that allows expressing code in few lines, but by that they keep it simple to read.
Even though I doubt that proper exception handling is shorter than the Go version. Many developers just choose to not do that and handle all Exceptions the same, or using try-catch blocks that stretch over hundreds of lines. It's indeed faster then, but not a good comparison.
> Speaking of not having maybe/option, every other line of code is "if (err == null)" and there's no way to abstract this out. Give me `option` dammit.
This is why I can't take Go apologists seriously when they say the language is just fine without generics. Ok, fine, maybe I don't need generics in my application code as much as I thought, but their absence has cascading effects throughout the language and requires you to do ridiculous things all over the place. Not just the constant "if (err == null)" and interface{}, but also go-generate, which I have seen Go defenders seriously try to claim obviates the need for generics. What?!
There are definitely legitimate use cases for generics, but `if err != nil` isn't one of them. You'll simply be pattern matching on an option type instead. Go generate is definitely not a substitute for generics.
> You'll simply be pattern matching on an option type instead.
If you have multiple things that can fail in sequence, you can "lift" into option and then the result of your expression will either be a real result or a short-circuited error.
Although I prefer exceptions, I like the way Rust approaches not having them.
It also communicates exceptions via option types, and - by convention - functions that can go wrong return an instance of Result<SuccessType, ErrorType> initialized either with a success value or an error value.
There are some helper methods on Result. I like "expect", which panics if an error is indicated and returns the success value otherwise. The real boilerplate removal comes via try!, which returns the result (actually it re-wraps it in a new Result) if it's an error. When used on functions that can fail inside functions which return Result, the result is that try! bubbles the exception up the stack.
The experience is very like traditional exception handling, but you always know exactly how control can flow, since try! only returns from the function it is directly inside, and can only from functions which advertise that they can fail by having Result<> as their return type.
> If you have multiple things that can fail in sequence, you can "lift" into option and then the result of your expression will either be a real result or a short-circuited error.
How is that different than with error return values?
> Or just use exceptions. Either would be preferable to C-style error handling.
I disagree. Exceptions seem to hide the problem. In Java and Python, I see people consistently fail to handle exceptions properly. The error paths are neglected because exceptions "hide" them. I would rather have a correct program with verbose error handling than a program that only has an elegant happy path but is otherwise incorrect.
If you can find a way to improve these code samples, please suggest it.
> Exceptions seem to hide the problem.
I haven't used Java in a while, but I recall a feature where it would force you to handle exceptions. It knows what kind of exceptions a function can throw, and it can check if they are exhaustively handled throughout the program.
Another poster already pointed out that you can call methods on value types, but you can also satisfy interfaces with primitive types. The bulk of your post is dogma, ironically.
It's not dogma. This was my experience using Go. I haven't touched go in a while though. Did they fix the interface{} problem?
Specifically, I ran into this when I was using a database library. Values that can be converted to a database-friendly format implement sql.Valuer. However, the library functions just took interface{} instead of sql.Valuer because primitive types could not implement sql.Valuer. Can they implement it now?
Btw, you don't get a free pass at my other objections just because someone poked a hole in one of them. The other problems still stand. Take them one by one.
Let's say library A decides to add func (i int) MyMethod() and library B decides to add func (i int) MyMethod().
Then library C calls i.MyMethod(). Which MyMethod is executed?
You can come up with an answer to that question (e.g. "pick random") but there is no sensible answer.
To avoid this ambiguity you can only add methods to your own types but not someone else's (i.e. not to types defined in other packages or basic types).
There never was an interface problem. The sql libraries opt to accept an `interface{}` instead of `sql.Valuer` as a matter of convenience (so you don't have to define the requisite methods). It wasn't a technical limitation.
> Btw, you don't get a free pass at my other objections just because someone poked a hole in one of them. The other problems still stand. Take them one by one.
You didn't mention other problems, you just whined about Go's feature set. You didn't support your opinions with anything debatable, so there's nothing to discuss.
> the language designers like to pretend it's not exception handling at all.
> You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever.
> There are built-in functions that use generics, but I'm not allowed to write one? Hypocrites.
The specific case I ran into was using a database library. The function that would compose a database query with custom parameters took interface{} as the parameter type instead of sql.Valuer. I thought this was a mistake, but when I asked the library's author, they said they had to make it take interface{} because primitive types cannot implement sql.Valuer.
Nothing in your comment indicates this is not still the case. You can't implement methods on primitive types, can you? That's a shame.
That's correct, and part of the larger notion that you cannot add methods to types from another module. (Primitive types come from the same pseudo-module that declares append, len, etc.)
Think about it. How would you cleanly implement this? If you allow e.g.
Then what does the compiler do when two modules declare that method? You could forbid it, but this will lead to situations where existing application code breaks because a library is updated and the new library version includes a method that was already implemented (possibly differently) by the application.
The other option is to allow implicit casts such as
func foo(s Stringer) { fmt.Println(s.String()) }
foo(1) # implicitly casts to ValueMeters to satisfy Stringer interface
But then you run into the same problem. How would you select (forward-compatibly!) the right interface implementation to cast to?
EDIT: The only solution that I can see is to allow function overloads:
Without having thought it through very far, I think that this can be done consistently (precisely because all types are final in Go once the module is cmplete), but it would probably still lead to some surprising behavior.
Actually, I just now see a problem with this:
func foo(x int) { ... }
func foo(x uint) { ... }
foo(0) # which one to use?
Literals don't have a type in Go until they're used in a place that requires a specific type. To make this example even more interesting, consider this:
func foo(x int) uint { ... }
func foo(x uint) int { ... }
fmt.Printf("%t", foo(0)) # prints "uint" or "int", but which?
ValueMeters isn't a primitive type, it's just convertible to one.
x := 1.0
y := ValueMeters(1.0)
x + y // Mismatched types error - ValueMetres and float64
Because Go doesn't believe in operator overloading, there isn't even anything you can do about it. You have to abandon the pretence and explicitly convert the values to the same type.
Go does a little magic with literals in that it will implicitly use explicit conversions on them to satisfy type constraints, so ValueMetres(1) + 1 works, but ValueMetres(1) + float64(1) does not. (Otherwise you end up with annoyances like not being able to add "1" to a float.)
This gets particularly gnarly when interacting with libraries which expect the underlying type you're wrapping:
It's a great decision if you don't want me to use your language. I'm already a little fed up with the camelCase vs snake_case coding style conventions I'm supposed to switch between to write an app that uses both Python and JavaScript. I don't need a third convention, especially not one that's enforced by the language and contrary to every other language in existence, as well as my serialization formats.
What would be the alternative? A keyword? `public`? `export`? May be you prefer but a lot of people prefer simple a simple convention over one more keyword.
I would prefer keywords because then if you change your mind about what should be public you don't have to hunt around for every reference to it and change its name.
Or, give me a convention where public stuff looks normal and private stuff looks weird, because then at least my JSON serializer will work. In Python you lead with an underscore to denote privateness.
You could use interfaces to communicate privateness. If something isn't exposed in an interface, it can be assumed to be private.
Or just don't have your compiler enforce privateness. You don't need it. Plenty of languages get along fine without it.
I like post hoc polymorphism -- there's nothing more frustrating than knowing that a class has all the functionality required to meet an interface but because the author didn't declare it as such it's unusuable. Implicit interface implementation is great when you happen to implement the right method names with the right signature perhaps by luck, or you define the interface based on the implementation but this is backwards. The problem is that if you want to implement two interfaces that share a method name or you simply named a method wrong, you're out of luck.
Personally I think Haskell has the best implementation of this I've yet seen in a programming language.
1. Types (structs) are defined in one place and contain only data.
2. Typeclasses (interfaces) are defined in another place and define a collection of functions (methods) and values.
3. Functions and values are defined in another place and operate on types.
4. In yet another place you can arbitrarily pair any type with any typeclass by specifying the mapping of functions/values that you've implemented with those required by the typeclass.
All four of these can be defined in separate modules, giving you the ability to allow any type (even ones you didn't define) to implement any typeclass (again, even ones you didn't define) using any implementation (even ones you didn't define) -- the implementation definition is separate from all three. Because the functions/values in tyepclasses are namespaced, there's no fear of name collision. The implementation of a typeclass by a type creates a static vtable in the compiler only, so there's no runtime penalty or dynamic dispatch. I would kill for this level of flexibility in an OOP language.
What exactly do you mean by conflicting? Remember that the types, typeclasses and function implementations are all namespaced, so name collisions here are fine. The only problem occurs when you define multiple instances for a type and a typeclass (e.g. Int is a Monoid as a sum and separately Int is a Monoid as a product) and you attempt to import both instances into the same module, called Overlapping Instances. But if the usage of the overlapping instances is not ambiguous it wont' cause any problems.
Even the following is perfectly fine:
-- sum.hs
module Sum where
instance Monoid Int where
mappend = (+)
mempty = 0
ten :: Int
ten = mconcat [1..4]
-- product.hs
module Product where
instance Monoid Int where
mappend = (*)
mempty = 1
twentyfour :: Int
twentyfour = mconcat [1..4]
-- usage.hs
import Sum
import Product
twohundredforty :: Int
twohundredforty = ten * twentyfour
This can break things (at compile time, unless you errantly mark something with an OVERLAPS pragma), which is why it's recommended that instances live where the type is defined or where the typeclass is defined. Instances defined elsewhere are known as "orphan instances", and trigger a warning (which you can disable) in GHC.
Edited to add: Note that the above applies mostly to libraries. There's not really a downside to defining orphan instances in an application so long as you guarantee consistency (and the best way to do that, IME, is to put all your orphans in one place, with only that file marked -fno-warn-orphans).
I agree that Haskell currently does it best (along with Rust, which IIRC does it identically). That said, there are two ways in which the situation is not perfect.
First, the dynamics you describe mean I can always add (which is great! that's usually what I need to do to get my job done). They don't help much in reorganizing. If I come out with a new package that introduces a typeclass, I'll usually provide instances for common types. If, down the line, we want to move those instances next to their definitions, both my library and theirs need breaking changes.
Second, related, if I want to provide instances for types that are provided by heavy-weight dependencies, other packages that might want to depend on me would have to depend on those dependencies.
Mutatis mutandis for if I am introducing the types and the libraries have the typeclasses.
Peeling instances out into separate packages means a multiplication of packages or retains a coupling of dependencies, and raises maintainability concerns around orphan instances.
I think the Go team probably wanted to avoid the mess that C++ has become, which to some extent has affected Scala as well. When a language tries to be all things to all people, it becomes cluttered and inconsistent. It can also become incompatible with previous versions in ways that prevent people from upgrading, as we have seen with Python 3.
However, the Go team has gone further than most in their rejection of changes, and I think it's reached the point where it hurts the adoption of the language. When I write Go code, I feel like Rob Pike is standing behind me telling me to do things the way he likes them done, or not at all.
There are plenty of examples of languages that have navigated this problem better than Go. Rust and Haskell come to mind immediately. C# and F# should also get more credit than they do for handling this kind of thing gracefully. Even Java 8 has incorporated many of the things people found attractive about Scala, without making them mandatory or alienating established developers.
Can you cite some examples of what you mean? I certainly agree on Scala--after trying it out for a while it definitely seemed to me Java was not so bad after all.
(Specifically I'm thinking of things like the problems of doing builds of Kafka across different Scala versions. This was a couple years ago so maybe things are better.)
The solution "go generate" reminds me of the class generating tools that Borland shipped with their C++ compilers, before they added support for templates, back in the MS-DOS days!
> I like generics, but why can't there be a language that doesn't have them?
Python,Ruby and Javascript don't have generics ... because they are dynamically typed. Go has generics, maps and array are container types, you just can't define your own. To say Go has no generics is stupid, it just doesn't allow you to define your own. Instead you can use catch all interface{} everywhere like dynamically typed languages.
> Not every language has to have generics, when I want to use them, I simply switch languages.
> Go has generics, maps and array are container types, you just can't define your own. To say Go has no generics is stupid, it just doesn't allow you to define your own.
That's usually what is indeed meant by the statement "Go doesn't have generics", maybe it should be "Go doesn't have user-defined generics", but I assume most people can infer that. I really hate the recent trend of having to spell everything out.
> This statement makes no sense at all.
Can you be more specific? What doesn't make sense at all in saying that when I have a simple problem that is unlikely to benefit greatly from a more sophisticated type system, I reach for Go, otherwise I use a different language?
And for people who don't get a choice, there is introspection and code generation. In Go, both of those options are, firstly a necessity, secondly far more complex than generics and thirdly introduce (more) weird project-specific exceptions to the type system that can't be reasoned about.
This article brings to mind the challenge of enforcing empathy in an engineering community. Software and other technical systems are coldly precise and unambiguous. People, on the other hand, are rarely that precise and often illogical without significant effort/investment into forcing themselves to be rigorous about a though or claim.
I think it's too easy for community members to leave opinionated outsiders with a bad taste in their mouth when they approach your system with opinions and tendencies that were built up in other systems with different circumstances and assumptions. The kneejerk response is "well, you're wrong, we don't do it that way here," hence the dogma.
It takes serious effort to fight that instinct, and not everyone is good at fighting it... especially when some community members suffer from the same problem as the outsider, but with the affinity being toward their own system. This is a hard problem to solve...
It's a good thing this article was written and there is a willingness to at least acknowledge the problem in the Go community. It contrasts to the style of say, Linus Torvalds, whose approach is basically "empathy? fuck that, I'd rather call them names" :-)
203 comments
[ 3.7 ms ] story [ 101 ms ] threadI don't have a good feel for whether that's true of the Go community, or maybe more importantly whether it's relatively more true of the Go community than, e.g., the Python community.
I too am rather surprised to see Go, Lisp and Haskell cited as languages designed by people who are not dogmatic. This may well be true at the micro-level of decisions like "how do you best compile green threads", but at the higher design levels all three languages are very, very opinionated.
I think statement in particular is questionable:
Contrast that kind of discussion with the heated arguments or overly zealous statements you sometimes see from users of the same languages. There's a real disconnect, possibly because the users don't have the experience of weighing the arguments on both sides and don't realize how easily a particular decision might have gone the other way.
What is this implying? The designers of Go and Haskell never make "overly zealous statements" or "heated arguments"? Isn't the history of Go littered with such arguments, the most famous being that Go is simple because it's designed for Google engineers, who can't handle complex languages?
It's definitely true that language communities are often zealous: when library interop is bad they have to be, in order to get an ecosystem of libraries and tools. That's why you see people posting things like "I made a terminal emulator in Rust" to Hacker News but don't see people posting the same for C++ or Java. Those languages have established ecosystems already and need much less evangelism.
Haskell developers have a clear position of letting features get in the language as the community pushes for, and the overall guidance is very much open to changes. The one thing you won't hear there is any equivalent of "generics are bad for you, we are not adding them".
Monads I can grasp, but never got how they are like donuts.
http://dtrace.org/blogs/wesolows/2014/12/29/golang-is-trash/
I think he's just trying to say that people should be less tribal and dogmatic about their tools, ,and users should try to understand that language design is about trade offs, not holy war. I think it is unhelpful to lump rsc in with rob pike, they are very different personalities.
There were a lot of discussions on generics, and the language authors and contributors were certainly open to ideas and indeed did their own fair share of research. For example, see https://research.swtch.com/generic from 2009. Indeed, the FAQ entry itself on Generics states 'Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do.', and looking at the blame log, that's from 2010 or earlier, and it's stuck to this day.
So, from everything I can see, the creators themselves are certainly open to the concept - I don't see any "you don't need generics" coming from that side.
https://github.com/golang/proposal/blob/master/design/15292-...
Only everybody who asks for generics knows that, and still wants them to go through and pay the price.
The "we'll only add them when there's a solution with no tradeoffs" is a red-herring, like "we'll add them on a day that doesn't end in -y".
>And I think most people will agree that Go has achieved a lot of success without them, so perhaps most use cases don't require generics after all.
In the same sense C has achieved quite a lot with buffer overflows, so what's the point in something like Rust?
And those people are outnumbered by people who are comfortable and productive with the current Go implementation.
Programming languages are not all placed on a single Bad<---->Good scale. There are many variables involved, and it makes sense to have local maxima like Java, Go, Clojure, C, Haskell, etc.
The only way to know that would be to be able to compare one implementation with and one without generics. But only one Go implementation exists. Perhaps there's some toy attempt by someone, but not anything that is equal in all other aspects to mainland Go but with Generics on top.
Plus, since most of the burden falls on the compiler writers and most of the benefits to end users, it just takes the compiler writers to be "comfortable with the current Go implementation" for this to continue to remain Generic-less.
1) tons of people have asked for Generics, few have asked for HKTs.
2) C++, C# and Java, programming languages with millions of users and extremely battle tested have Generics, no mainstream language at that level offers HKTs.
3) As a result of (2) a ton more people are familiar with Generics than HKTs, and so the latter are much less probable to be requested.
And of course, if they do add Generics, and people ask for HKTs, they should consider what they do about that then and there, not now.
No reason to never make a step towards somewhere just because you might (or will) be asked to also take a further one.
Except if they believe that they are already at some optimal point (which is my case I think).
All I'm saying is this doesn't seems like a simple equation to me. There are many possible solutions to the problem of building large, correct, maintainable, performant programs - and neither of the existing ones looks like a surefire winner given all the limitations.
https://golangnews.com/stories/1490-results-of-the-gopher-la...
Personally, I love most things about go, but its utter clumsiness at iteration and data transformation relegate it to a fairly limited number of use cases in my mind.
I do also really value the lack of change in the language though, it is important and rare that it is stable and not accreting features every year (compare with rust or c++).
In fact, because I used Clojure just before switching to Go, I found not having immutable variables harder to get into the habit of more than anything else. I actually first used Go because I needed to write something that manipulated bits in memory reliably, and couldn't be bothered going back to Java.
As a counter-example, I'm not sure this is true.
Java didn't have generics. Then they didn't have lambdas. Now they're even redoing the classpath! Have they gone nuts?
No, they've been evolving, and they've kept on adding features as it was opportune and useful to do so. If tomorrow's programming paradigms depend on HKTs and dependent types, then, yes, Go might want to add them. Today's programmers are just about starting to understand what a dependent type even is, so you're suggesting to include them in Go in order to ridicule the potential inclusion of generics. This is a fallacy, because generics and dependent types are years, decades, apart. And generics are very much part of how several generations of programmers have learned, and expect to be able to program.
If your language stops changing, your language is already dead. Yet another point of similarity between natural and programming languages.
As a aside, why not add HKTs straight with generics? The problem the Go implementors have with generics seem to be about run-time representation (boxing or unboxed), but no additional run-time issues arise from HKTs. The main issues of HKTs are that type inference becomes harder. Haskell's approach of eschewing type-inference at the kind level (because it's undeciable anyway) but giving unkinded type variables the kind * seems to be a run-away success.
Irrelevant. Go already has parametric functions like append, it just doesn't allow user defined ones. append isn't enough when a programmer needs to remembers 10+ tricks to make up for the lack of generics :
https://github.com/golang/go/wiki/SliceTricks
Citation needed. How do we know that the majority of Go users would prefer not to have generics? How do we know the number of non-Go users would be willing to use Go if it had generics is insignificant? Is there some large scale survey of Go users somewhere you can cite?
Of course they are. The people who are not comfortable with the current implementation aren't using Go.
It's a fallacy to only look at what the major users wants. It's similar to the “faster horses” fallacy.
Not true. What about those who write Go at work for instance?
Russ actually addressed this at length: https://news.ycombinator.com/item?id=9622417
But Generics are neither some new PL topic, nor have they not passed the "filter of practical experience" (millions use them in C++, Java and C# alone).
I'd rather they explicitly said: "It will take a total breakthrough in computer science regarding generics implementations for us to consider them for Go, and we wont ever care of paying the costs of any current, viable in practice for millions of programmers in other mainstream languages, approach".
Well I (somewhat) agree with that, but my take of it was Russ is basically saying existing approaches "don't work well with Go" and "recent ideas" are "not well understood" with the concern that "there's a princess in another castle after that one I am sure."
Or put it another way, per his emphasis on the "engineering" nature of Go, it is not an issue of tradeoffs, rather a concern that a runaway train of complexity will follow. Or if you will excuse my Mexican French, the problem is the fucken type system :)
A programming language design is the combination of a set of features. The hard part in designing a language is working out how all these features interact with each other.
For example, one feature of Go is that every type has a default value (0 for decimals, nil for pointers, empty string for strings, etc.). This property leads to the inability to include algebraic data types cleanly. To see this, consider this simple ADT (in Haskell syntax):
What's the default type here? It could be "Left(default-value-of-a)" or "Right(default-value-of-b)". You could introduce additional syntax to define the default value, but that increases complexity. You could introduce a rule how the default value is inferred in these cases, but that goes against the Rule of Least Surprise. You could remove the "default value for every type" feature that clashes with ADTs, but that causes problems elsewhere. Therefore Golang lacks ADTs and uses pointers and multiple return values instead.This is a dangerous and slippery slope to engage on.
Java was also extremely successful in 2004, before it supported generics. Yet you'd be hard pressed to find a Java developer who thinks that generics were not overwhelmingly beneficial to the language, and probably one of the main reasons why Java is even more dominant and powerful today than it was pre-generics.
I am not saying generic are not useful. But I have not found enormous use of Generics in typical Java business application. The most common generic data structure I have used in Java are Maps and lists which are already generic in Go.
Thanks to generics, millions of lines of code in libraries that you are using became safer and faster, and you are benefiting from this, even if it's not apparent to you.
Basically, generics made Java a better and safer language. Both theoretically and practically.
It's hard to argue that a feature that appeared a decade later changed the equation all that much. I like generics but would still use Java even if they did not exist. In my work the concurrency features are far more important.
Designers of Go have been disciplined enough to keep things simple - let's you keep more of the problem domain you're trying to solve, along with the code to solve it, in your head. Try that with a big project using another toolchain - lots of frameworks, lots of places to plug your code, with less coherency (in my experience) across the various experience levels of your team. And I'm talking about big teams here, not 10 devs.
Subjective Success metric with comparable teams: Go vs <x with generics>. If Go team gets solution done faster, correct, within budget compared to other team, your stakeholders will not give a crap about what devs think about generics. There's a good chance that would happen, based on my experience.
Java's current popularity is due to Android, not the bloated corpse of enterprise Java.
Android certainly made it more popular than it already was, but Java was extremely popular way before Android appeared.
If Java had generics from day one, they could have put methods like equals and hashCode in interfaces that some classes don't implement, instead of passing static type checks and then blowing up at runtime because you did something that never made sense.
Internally, the Go team has drawn up proposals for adding generics [1] but rejected them because of drawbacks. Hopefully one day we can see those drawbacks, because maybe a lot of us are more than happy to pay that price.
[1] https://www.reddit.com/r/golang/comments/46bd5h/ama_we_are_t...
From the discussions I've seen on what Go considers complicated I'd bet it work out to be something like name mangling.
Go turns off ASLR for loaded C code because its easier to debug. Which is really only true if your debugging process requires memorizing the absolute address of symbols that control flow will jump too. I guess that is useful if you are reading the raw hex streams.
Name Mangling is complicating things because you can no-longer debug with ElfRead.
Go seems to reject anything that was invented after the ~70's. As those concepts are complicated.
I can't help but feel that Thompson and Pike just don't want to change the bad habits they've developed from by-gone ages and adopt modern tooling.
Why wait for "one day"? These proposals are public: https://github.com/golang/proposal/blob/master/design/15292-...
They should have thought about it at the very beginning of Go design. it's easy to say now "We didn't find a good way to implement generics" when their design choices made implementing generics insanely hard at first place.
Well actually,they thought about it, where does append,delete,make and co come from? how do these functions know their arguments and return types at compile time? generics.
Furthermore it's not all or nothing. Go could have supported parametric functions in user land just like append,make or delete without implementing full-on generics. They just don't want to bother with that, because it's impossible to retrofit generics now without breaking the reflect package. The rest is just excuses to evade the issue.
> Generics are not free
Creating a modern statically typed language WITHOUT generics isn't free either. Just like implicit interfaces are not free, just like the reflect package is not free, just like using interface{} somewhere isn't free, just like telling people to use code generators isn't free.
That is, a []Whatever is not as opaque as a Whatever{} or an interface{}. Same with a map[Foo]Bar, you (you being Go) know it's a map.
(I have no opinion on whether Go should have generics, I just think the apparent exceptions make sense even if they might seem unfair.)
https://github.com/golang/proposal/blob/master/design/15292-...
Go doesn't have generics because they don't want them.
https://research.swtch.com/generic https://news.ycombinator.com/item?id=9622417
Still, if you really feel passionately that Go with generics would be frickin' awesome, generics seem like a pretty easy thing to write a preprocessor for. With the great knowledge of programming language design and implementation that most pro-go-generics internet forum posters have, it should be a piece of cake.
I am basically doing this: https://github.com/lukechampine/ply
Ply is like Go except you can write things like []int{1,2,3}.filter(even) and it will Just Werk.
It's not quite ready yet, but it feels promising. The idea is that Go doesn't really need full support for generics; most people just want a few more higher-order functions available for manipulating slices, maps, and channels.
It's how C++ started. Unfortunately, that's how it will also end for Go, because of Go designers ignoring the past.
That said, I'm curious to know if Have is able to eliminate intermediate structures (deforesting) when chaining together transformations. Haskell performs "stream fusion" to accomplish this, but I'm not aware of how it's done in other languages. Ply does support this optimization, and that's mostly possible because it restricts the generics to a built-in set (rather than allowing the programmer to define their own generic functions).
My other goal with Ply is to make it as familiar as possible to Go programmers. It's not so much inventing a new language as it is scratching an itch.
Your second sentence answers your first. Not everyone likes writing the same code over and over again, even if it's simple code.
> generics seem like a pretty easy thing to write a preprocessor for
Very funny. But even if it were possible, the bigger problem with using a preprocessor is that everyone ends up using their own incompatible generics preprocessor.
Others have pointed out this is how we got C++ and stopped putting method dispatch and stack unwinding boilerplate at the same level as the code we really care about.
It definitely works out. There's just a question of whether it's an end result that we want.
http://en.cppreference.com/w/c/language/generic
There were many motivations for C++ -- and many more seemed to get added as it went along -- it's not just generics parametric polymorphism. Of course I would hate to say: Go should have every language feature or else we'll end up with Go++ or Objective-Go or something like that. But simplicity is not just a matter of not providing for something.
The general consensus from the Go developers is probably best summarized as "Generics are useful, but each of the implementation methods we have seen so far have significant tradeoffs, and the benefits are outweighed by the costs of the methods we've seen."
Which isn't to say that Go will never see generics, if someone finds the right way to implement them.
I think what people tend to disagree with is the last part. There is a lot of leeway in estimating the costs and benefits and people end up with very different estimates.
The truth is that one does not need generics. I've worked for decades in languages that don't have generics. The real question is with current software capabilities and requirements, is the convenience provided by adding generics to Go worth the cost of the added complexity. People clearly disagree, and the language designers decided "no". That's not dogma; that's engineering.
Sure ,when you can use interface {} AKA void pointers everywhere, you certainly don't need generics /s
You need generics if you want to avoid context.Context.Value(interface {})interface {} like API. You need generics if your goal is type safety AT COMPILE TIME. Obviously it's not one of the goals of Go /s ...
https://groups.google.com/forum/#!msg/golang-nuts/Rj5t1h_ztx...
Sure, the final implementation still has a couple instances of interface{}, but they are much more innocuous than your example.
The net/context package was designed to pass request lifetime context throughout your service infrastructure (think microservices), where the values provide metadata from your transport protocol. HTTP headers, if we want to think in terms of HTTP, although obviously there is no specific protocol dependency at this layer. The value may not even be assigned by a Go program, so it seems that runtime validation would be a necessity even with generics. A broken or malicious program upstream could easily inject something into the context that you weren't expecting, or simply not supply the value at all.
So, if you want to use types to make Context safer to use - and I'm not saying this is necessarily desirable, but assuming it is - you could start with something that works in the existing Go language: explicitly marking what kinds of objects can be keys rather than using interface{}.
(Incidentally, I find it odd that WithValue is a freestanding function while Value is a method; I suppose this is to reduce the burden of reimplementing the Context interface for a new type, but is there any actual reason to want to do that?)This helps prevent you from accidentally using the wrong object as a key, a bug that might be tricky to catch if the key you meant to use is legitimately sometimes not present in the Context (in which case your code wouldn't error out due to nonpresence of the wrong key). It also prevents hacks like using strings as keys in lieu of declaring dedicated constants, which would create the risk of silent failure due to typos. (It seems this is already considered a bad practice, but such a signature would enforce it.)
Once you do that, it's apparent how generics could further improve safety. Using imaginary Go syntax:
Now you're safe from type mismatches. Actually, it seems that best practice is already for packages to provide type safety by defining wrapper functions, such as this from userip: That works well enough, but generics save you the boilerplate; packages could just expose the keys as public constants while retaining type safety. Admittedly, those two functions are only a few lines to define, so there's not that much boilerplate to save, but it's something. Generics would also make it possible to write type-safe generic helper functions involving contexts, though tbh I can't think of many obvious ones you'd want. Maybe a function that panics if it can't find the key: ...though that would be better handled by a proper Option<T> type anyway. But that too would be enabled by generics!Okay, but there's still more that the compiler could guarantee. The value is now guaranteed to have the right type if it's there, but for all we know it could be missing. Indeed, this is probably a bigger footgun than types are in practice: it's easy to accidentally have some code path that bypasses the code responsible for setting a particular key. Unfortunately, this is a harder problem to solve even with generics, and in fact multiple Rust web frameworks have run into very similar issues:
https://github.com/nickel-org/nickel.rs/issues/79
https://www.reddit.com/r/rust/comments/2cl7bw/web_frameworks...
Arguably what you really want here is extensible records with a structural type system. For e...
The fact that interface code often has runtime failures is usually due to a feature Go interfaces get you that many languages with generics don't have in the first place -- the ability to downcast to concrete types.
Now, interfaces sadly do not have the same amount of type safety at compile time for defining new containers and you can't do things like return value polymorphism as well, but it's not that bad.
It requires a different style of programming (the post from rsc linked in the sibling comment is particularly good at illustrating this), but it's not that bad.
I say this as a Rust programmer (also a Go programmer, but primarily a Rust programmer) who absolutely loves generics.
you're assuming you know the kind of problems I'm trying to solve everyday. You don't. Don't patronize me.
I write a lot of algorithms that can work with float, double, complex float, and complex double. Without generics/templates (or a macro pre-processor), I have to cut and paste that code 4 times, or I have to sacrifice performance. Maybe I don't need templates, but I really really want them.
Just to be clear, I personally dislike tabs and line-lengths over 80 characters. However, such choices have no bearing on the success or failure of any project I have ever experienced.
Cross-project style consistency doesn't buy us anything reasonable--there's no "engineering" about it.
Also, braces around conditional logic isn't what I'm talking about here (since that's not a "go fmt" style choice--that's the language syntax).
Most project teams have limited resources to come up with a solution that is close enough to correct to make the project pay off. In new technology there also tends to be a small set of technical issues that are hard and that you must get right--nothing else matters that much if you fail on the core. I am pretty heartless about anticipating things like code conventions, security requirements, legal review, etc. and just following the rules or otherwise making them non-issues for the project team in areas where it does not fundamentally alter the outcome.
Edit: clarification
Their argument has never been that you don't need generics. It has been we don't want to add them in the currently available forms right now and here are the ways you can get around their absence in the meantime.
All of those things are more valuable the more people that use them, and if you've invested money or time into any of them, you have more of a pay off on your investment if more people also use them. So you might feel strongly incentivized to defend your platform of choice while attacking competing platforms, even if another platform might be marginally better, but not enough to justify the switching costs.
If you're a language developer, you have dual incentives-- one is to get people to use your language, which would seem to encourage you to engage in holy wars, but the other is to be good at developing languages which requires you to think objectively about what you and other language developers are doing, so even if you're publicly defending your choices, you should be open to the idea that another way might be better.
I find the holy wars are usually fueled by "fans" -- people less invested and less knowledgable in the topic. This is true of movies, bands, sports teams, phones, computers and cars.
The fact is if you work on the tech you appreciate that there are tradeoffs and know what your overall objective is (so are less likely to argue about things that don't get you there). Musicians tend to get along -- did the Beatles hate the Stones (OK, the Stones released "Let it Bleed"). I doubt Mark Hamill hates Star Trek or Shatner Star Wars. The catholic pope meets with the grand mufti of Jarusalem. Obama and Bush talked a lot in 2008 and 2009.
It's like the joke about academia being so cutthroat as the stakes are so small. Holy / Flame wars erupt among those on the periphery at best.
There seems, IME, to be a sizable contingent of people who have invested heavily in a particular technology and are threatened, in their own market value, by threats to its distribution, in most technology flamewars. They aren't necessarily less knowledgeable, but frequently prefer depth in a platform to breadth across platforms for developing their own skill set, which creates a strong incentive to promote the platform.
I used to see it as a humorous statement about the disconnected irrelevance of academia.
As a professional academic, I now read it as 'the stakes are so few'. Academic politics are rigidly contained by existing structures and limited funding. There may only be one or two ways to 'win' within the framework, with a limited number of 'winners'. In such an environment, vicious politics may be unsurprising.
The language that I speak defines the limits of my world.
I've always thought it was particularly apt for programming languages.
https://en.wikipedia.org/wiki/Linguistic_relativity
I personally rather suspect that the weaker forms (that language generally shapes but not fully limits how you view the world) are also equally fallacious, even if language and world-views might be weakly correlative--instead, I see language and world-view as essentially both being strongly derived from that of those who teach you.
For example Mathematics is generally accepted to be incomplete it what it can express (Gödel), and programming languages, while expressive and in theory turing complete, still limit the thoughts you can easily express, which I suspect will guide users to do things a certain way.
Natural language is a bit different because of borrowed words and the flexibility of the humans parsing it.
("diagram chasing" in category theory might be an example of thought being almost eliminated by a notation...)
Something that would only be said by someone who never thought about the time and space complexity of theorem proving algorithms. (Given his areas of contribution, I can't be sure if he ever did or he didn't.)
In other words, not all human cognition is "thought", for Russell. So I don't think he's making any claim that implies total absence of computation with a perfect notation. I'd say he's in the clear on this one.
That's a nice-sounding hypothesis, but it's difficult to falsify. Steven Pinker reviewed some of the scientific literature on that idea in "The Stuff of Thought" and it mostly holds up the null hypothesis.
I believe that hypothesis is true in the trivial sense -- nearly everything you learn affects the way you think -- but I'm not convinced it's true in the more interesting sense.
That's a very generous definition of similarities, as tons of languages have green threads, even Java/JVM had them initially (and still has them in various implementations over native threads). Could even add that most languages also have GC, some type system, io, modules, etc.
compiles to machine code
have green threads
use non-blocking IO in the background
have parallel runtime
Haskell is the first that comes to my mind. (I guess that Common Lisp is a safe bet too, as it had everything for ages before it was cool;), but I'm not familiar with that domain to be sure) Erlang have everything except machine code compilation.
What else? I guess the rest is from the newer, lesser known or experimental languages. So is there anything more popular than Haskell?
I think Julia is there too, not sure.
- compiles to machine code, check. A whole operating was written in it, AOS (aka BlueBottle).
- have green threads, check. That is where the name Active comes from. It makes use of tasks.
- use non-blocking IO in the background, check. Tasks are used everywhere across whole OS.
- have parallel runtime, check. The whole OS makes use of parallelism and its compiler, Paco, does compilation of modules in parallel
https://en.wikipedia.org/wiki/Active_Oberon
http://www.ocp.inf.ethz.ch/wiki/Documentation/Front
http://www.progtools.org/article.php?name=oberon§ion=com...
I majored in systems and distributed programming, and had the luck of having a set of teachers that cared we should know the history of computing, making us curious about our past and how those designs could influence current ones.
So the habit of hunting and reading papers stayed with me, even if during the day I just do boring enterprise consulting.
Many times they are more entertaining than reading newspapers on the daily commute.
compiles to machine code: yes
have green threads: I think DispatchQueues should qualify for this
use non-blocking IO in background: yes
have parallel runtime: I am not sure
A way to fight this is to make sure you belong to more than one language community. Then you'll be forced to not make such a simplified model.
"Go" is such a poor name, both for a language and game, because they collide with the English word, it's impossible to perform targeted search or sometimes even to know what an article is about.
Some TV shows like "24" suffered from the same fate. On reddit the game of Go is represented by the Korean "baduk", probably to escape the confusion cone.
https://www.google.com/search?q=c+stdlib
https://www.google.com/search?q=c%2B%2B+stdlib
("Golang" is a lot less problematic, of course. Hell, in an alternate universe, why not "Gol"? Only one more letter, and no serious collisions I can see...)
Except millions of sports-related material, the brazilian airlines and so on and so forth :)
Unless you're writing in Perl or javascript, almost every programming that exists is either a short English word, a person's name, or just a letter with maybe some punctuation.
There's also the fact that (at least) Google uses your private information, global search trends and whatever else to try to make a better guess of what you mean. In my case, just writing "Go" gives me back golang.org as the first result.
It's more unusual for a programming language to achieve success WITHOUT a naming collision with some common English word or proper name.
But then you still have to follow the rules of the UX, consistency, flexibility, all that, and none of the languages I know of are serious about that. It's all just dogma everywhere, or "engineering trade offs" if you like. We don't have much dogma only when it comes to performance, since it's the only thing easily measurable.
There Is More Than One Way To Do It.
Regardless of the community, we generally all strive for the most efficient way to accomplish a goal, but the definition of "efficient" can vary: execution performance, elegance, ease of developing, ease of integrating into X... Often, one or a few leading solutions emerge but it's tempting to take those pragmatic optimums and turn them into law—or worse: follow them for the wrong reasons.
It's a good idea to know why you're using a particular design tradeoff, and remain flexible to change if you find your constraints have changed. Some environments make this interchangeability easier than others but there's almost always a choice.
I really think this is a great post, and emphasizes a strong danger in adopter culture. But I do also think Go, in particular, is rigorously dogmatic in it's tooling. To good effect, in that anyone can come to any project and understand some basics, but the workspace decision is really hard to reconcile with the project-by-project view of the world that most language tooling assumes.
There's not much of that. I think that most people who are put off by that are actually angry that Go didn't affirm their own prejudices.
It has this great automatic interface system! Amazing! Except it also has "primitive" types that can't participate, so many of my functions have to take interface{} instead which is pretty much void*.
It can dispatch to different functions based on the argument types! It will even automatically switch to the non-pointer type if you pass a pointer to a non-pointer function. However, you can't write a pointer and non-pointer version of the same function. Also why are we using pointers to represent reference mutability and optionality these days? Other languages have maybe/option which handles optionality much better.
Speaking of not having maybe/option, every other line of code is "if (err == nil)" and there's no way to abstract this out. Give me `option` dammit. Exception handling exists via panic, but it's a little different than you're used to and the language designers like to pretend it's not exception handling at all.
There are built-in functions that use generics, but I'm not allowed to write one? Hypocrites.
You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever. It also gets extremely annoying when you want to serialize json, serialize a protocol buffer, or do a database query. Name your struct members in lowercase and those libraries won't be able to see them, but name them in uppercase and the output will be in the wrong case. Instead, you have to "annotate" those struct fields repeatedly for every library you're going to pass them to. You also have to manage "naming convention" systems that translate between the two names, and these conventions are never the same between two libraries.
I wouldn't say Go has structural typing. It has a form of interfaces, which are not the same.
Structural typing is when the data members of an object are expressed in its type. For example, `x : {f : t}` is a statement that the object `x` has a member called `f` of type `t`. That is structural typing.
On the other hand, what Go has is pretty much the definition of interfaces, where you have constraint types that indicate that anything satisfying the constraint has some functions defined on it.
function :: forall a. { f :: String, g :: String | a } -> String
This is a function that takes an extensible record. i.e. 'a' is some type which has fields f of type String and g of type String, at minimum.
Go's structural typing is based on methods instead of fields, but it's structural typing as well.
This in particular is maddening when learning go. I lost hours to this when deserializing JSON.
I remember this biting me when I wanted to write a function that would interpret the value I passed to it, and have a special case for null pointers. I wanted to also be able to pass a non-pointer to this same function name and get the standard case, but the dispatch would not let me do this unless I gave both versions of the function different names.
THIS.
One thing that definitely bothers many people, myself included.
roughly how much Go would you say you've written...?
What point are you making?
If someone writes go for an hour they'll see this pattern.
Error handling in go is more verbose than exception based error handling in some other languages. /shrug
I prefer it, but it's true.
It's obvious the point being made here, regardless of the wrong null/nil thing.
Checking for an error to be nil seems unusual, as you'd continue with your code in that situation.
And the code isn't that long, don't think it really bloats up the code.
The point being made was go has verbose error handling. It does. So does rust; we don't need to dance around it.
Better is a different question, but that wasn't the point the OP was making.
It's the same as not having a ternary operator. Go is no language that allows expressing code in few lines, but by that they keep it simple to read.
Even though I doubt that proper exception handling is shorter than the Go version. Many developers just choose to not do that and handle all Exceptions the same, or using try-catch blocks that stretch over hundreds of lines. It's indeed faster then, but not a good comparison.
This is why I can't take Go apologists seriously when they say the language is just fine without generics. Ok, fine, maybe I don't need generics in my application code as much as I thought, but their absence has cascading effects throughout the language and requires you to do ridiculous things all over the place. Not just the constant "if (err == null)" and interface{}, but also go-generate, which I have seen Go defenders seriously try to claim obviates the need for generics. What?!
If you have multiple things that can fail in sequence, you can "lift" into option and then the result of your expression will either be a real result or a short-circuited error.
https://drboolean.gitbooks.io/mostly-adequate-guide/content/...
Or just use exceptions. Either would be preferable to C-style error handling.
Hey, look, a monad! \o/
It also communicates exceptions via option types, and - by convention - functions that can go wrong return an instance of Result<SuccessType, ErrorType> initialized either with a success value or an error value.
There are some helper methods on Result. I like "expect", which panics if an error is indicated and returns the success value otherwise. The real boilerplate removal comes via try!, which returns the result (actually it re-wraps it in a new Result) if it's an error. When used on functions that can fail inside functions which return Result, the result is that try! bubbles the exception up the stack.
The experience is very like traditional exception handling, but you always know exactly how control can flow, since try! only returns from the function it is directly inside, and can only from functions which advertise that they can fail by having Result<> as their return type.
How is that different than with error return values?
> Or just use exceptions. Either would be preferable to C-style error handling.
I disagree. Exceptions seem to hide the problem. In Java and Python, I see people consistently fail to handle exceptions properly. The error paths are neglected because exceptions "hide" them. I would rather have a correct program with verbose error handling than a program that only has an elegant happy path but is otherwise incorrect.
Compare: https://gist.github.com/nickretallack/1462f2652cda6fc2a97283...
If you can find a way to improve these code samples, please suggest it.
> Exceptions seem to hide the problem.
I haven't used Java in a while, but I recall a feature where it would force you to handle exceptions. It knows what kind of exceptions a function can throw, and it can check if they are exhaustively handled throughout the program.
Specifically, I ran into this when I was using a database library. Values that can be converted to a database-friendly format implement sql.Valuer. However, the library functions just took interface{} instead of sql.Valuer because primitive types could not implement sql.Valuer. Can they implement it now?
Btw, you don't get a free pass at my other objections just because someone poked a hole in one of them. The other problems still stand. Take them one by one.
Let's say library A decides to add func (i int) MyMethod() and library B decides to add func (i int) MyMethod().
Then library C calls i.MyMethod(). Which MyMethod is executed?
You can come up with an answer to that question (e.g. "pick random") but there is no sensible answer.
To avoid this ambiguity you can only add methods to your own types but not someone else's (i.e. not to types defined in other packages or basic types).
> Btw, you don't get a free pass at my other objections just because someone poked a hole in one of them. The other problems still stand. Take them one by one.
You didn't mention other problems, you just whined about Go's feature set. You didn't support your opinions with anything debatable, so there's nothing to discuss.
> the language designers like to pretend it's not exception handling at all.
> You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever.
> There are built-in functions that use generics, but I'm not allowed to write one? Hypocrites.
func (i myInt) Foo() {}
Can you elaborate? A primitive value can easily implement an interface if you assign it additional meaning through a type:
Nothing in your comment indicates this is not still the case. You can't implement methods on primitive types, can you? That's a shame.
Think about it. How would you cleanly implement this? If you allow e.g.
Then what does the compiler do when two modules declare that method? You could forbid it, but this will lead to situations where existing application code breaks because a library is updated and the new library version includes a method that was already implemented (possibly differently) by the application.The other option is to allow implicit casts such as
But then you run into the same problem. How would you select (forward-compatibly!) the right interface implementation to cast to?EDIT: The only solution that I can see is to allow function overloads:
Without having thought it through very far, I think that this can be done consistently (precisely because all types are final in Go once the module is cmplete), but it would probably still lead to some surprising behavior.Actually, I just now see a problem with this:
Literals don't have a type in Go until they're used in a place that requires a specific type. To make this example even more interesting, consider this:Go does a little magic with literals in that it will implicitly use explicit conversions on them to satisfy type constraints, so ValueMetres(1) + 1 works, but ValueMetres(1) + float64(1) does not. (Otherwise you end up with annoyances like not being able to add "1" to a float.)
This gets particularly gnarly when interacting with libraries which expect the underlying type you're wrapping:
One of Go's best decisions ever IMO. To each their own :)
Or, give me a convention where public stuff looks normal and private stuff looks weird, because then at least my JSON serializer will work. In Python you lead with an underscore to denote privateness.
You could use interfaces to communicate privateness. If something isn't exposed in an interface, it can be assumed to be private.
Or just don't have your compiler enforce privateness. You don't need it. Plenty of languages get along fine without it.
I like post hoc polymorphism -- there's nothing more frustrating than knowing that a class has all the functionality required to meet an interface but because the author didn't declare it as such it's unusuable. Implicit interface implementation is great when you happen to implement the right method names with the right signature perhaps by luck, or you define the interface based on the implementation but this is backwards. The problem is that if you want to implement two interfaces that share a method name or you simply named a method wrong, you're out of luck.
Personally I think Haskell has the best implementation of this I've yet seen in a programming language.
1. Types (structs) are defined in one place and contain only data.
2. Typeclasses (interfaces) are defined in another place and define a collection of functions (methods) and values.
3. Functions and values are defined in another place and operate on types.
4. In yet another place you can arbitrarily pair any type with any typeclass by specifying the mapping of functions/values that you've implemented with those required by the typeclass.
All four of these can be defined in separate modules, giving you the ability to allow any type (even ones you didn't define) to implement any typeclass (again, even ones you didn't define) using any implementation (even ones you didn't define) -- the implementation definition is separate from all three. Because the functions/values in tyepclasses are namespaced, there's no fear of name collision. The implementation of a typeclass by a type creates a static vtable in the compiler only, so there's no runtime penalty or dynamic dispatch. I would kill for this level of flexibility in an OOP language.
Even the following is perfectly fine:
Edited to add: Note that the above applies mostly to libraries. There's not really a downside to defining orphan instances in an application so long as you guarantee consistency (and the best way to do that, IME, is to put all your orphans in one place, with only that file marked -fno-warn-orphans).
First, the dynamics you describe mean I can always add (which is great! that's usually what I need to do to get my job done). They don't help much in reorganizing. If I come out with a new package that introduces a typeclass, I'll usually provide instances for common types. If, down the line, we want to move those instances next to their definitions, both my library and theirs need breaking changes.
Second, related, if I want to provide instances for types that are provided by heavy-weight dependencies, other packages that might want to depend on me would have to depend on those dependencies.
Mutatis mutandis for if I am introducing the types and the libraries have the typeclasses.
Peeling instances out into separate packages means a multiplication of packages or retains a coupling of dependencies, and raises maintainability concerns around orphan instances.
IIRC, Backpack might fix this.
With designing APIs, that reaction is used as a proxy for "doing it right."
You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever.
Some Smalltalks used this convention with Globals.
Actually, Microsoft created a languages like C#, F#, Visual Basic that promote -- though do not enforce -- this convention
> Do use Pascal casing for all public member, type, and namespace names consisting of multiple words.
https://msdn.microsoft.com/en-us/library/ms229043.aspx
C++ often has capitalization for methods (which often happen to be public) but not member (which often happen to be private).
Capitalization in Ruby denotes a constant, enforced by the runtime.
Java uses ALL_CAPS for public static constants and lowercase for private ones.
Capitalizing public things is not that weird.
However, the Go team has gone further than most in their rejection of changes, and I think it's reached the point where it hurts the adoption of the language. When I write Go code, I feel like Rob Pike is standing behind me telling me to do things the way he likes them done, or not at all.
There are plenty of examples of languages that have navigated this problem better than Go. Rust and Haskell come to mind immediately. C# and F# should also get more credit than they do for handling this kind of thing gracefully. Even Java 8 has incorporated many of the things people found attractive about Scala, without making them mandatory or alienating established developers.
(Specifically I'm thinking of things like the problems of doing builds of Kafka across different Scala versions. This was a couple years ago so maybe things are better.)
Ultimately, people will write a Go++ because the language isn't good enough. That's not avoiding C++, that's forcing people to recreate C++ .
If I want a simple language, no generics I go with Go, for everything else I go with Rust and Swift.
Not every language has to have generics, when I want to use them, I simply switch languages.
There is something nice about being able to keep Go in your head and I think that it has shown that there is a market for such a language.
Python,Ruby and Javascript don't have generics ... because they are dynamically typed. Go has generics, maps and array are container types, you just can't define your own. To say Go has no generics is stupid, it just doesn't allow you to define your own. Instead you can use catch all interface{} everywhere like dynamically typed languages.
> Not every language has to have generics, when I want to use them, I simply switch languages.
This statement makes no sense at all.
That's usually what is indeed meant by the statement "Go doesn't have generics", maybe it should be "Go doesn't have user-defined generics", but I assume most people can infer that. I really hate the recent trend of having to spell everything out.
> This statement makes no sense at all.
Can you be more specific? What doesn't make sense at all in saying that when I have a simple problem that is unlikely to benefit greatly from a more sophisticated type system, I reach for Go, otherwise I use a different language?
I think it's too easy for community members to leave opinionated outsiders with a bad taste in their mouth when they approach your system with opinions and tendencies that were built up in other systems with different circumstances and assumptions. The kneejerk response is "well, you're wrong, we don't do it that way here," hence the dogma.
It takes serious effort to fight that instinct, and not everyone is good at fighting it... especially when some community members suffer from the same problem as the outsider, but with the affinity being toward their own system. This is a hard problem to solve...
It's a good thing this article was written and there is a willingness to at least acknowledge the problem in the Go community. It contrasts to the style of say, Linus Torvalds, whose approach is basically "empathy? fuck that, I'd rather call them names" :-)