Go is an example of how to be extremely successful by catering to the needs of the project's core audience (well-rounded stdlib, extremely fast GC, short build times, trivial deployment, etc) while paying much less attention to the vocal minority's complaints (generics, package system, etc).
But I think there is more to it. I have used Go (almost) exclusively for private toy programs I write in my free time to relax (sounds weird, I know), so my perspective may be warped. But something about is very compatible with the way my mind works. With some other languages, say C or C#, I find myself constantly browsing through documentation to figure out what a given construct means in that language. And don't get me wrong, I like both of these languages.
But with Go, my intuition what I think a given piece of code should mean is nearly always in line with the language specification. The only other language I had this level of rapport with is Python.
There are many things I miss in Go, but all in all, I think it is simplicity done right.
BUT if Go had not also gotten the things right that you mention, it probably would not have become this popular.
> my intuition what I think a given piece of code should mean is nearly always in line with the language specification.
for some things yes, but for others I think that it's more familiarity than intuition, take for example interface slices, you start by learning that you can assign any type to interface{}, so intuitively you'd think that you could assign any type slice to []interface{} but you find out soon enough that doesn't work
If you dig a bit it is completely understandable why it doesn't given how interfaces are laid out in memory, but from an intuitive standpoint you'd think that since you can do it in the scalar case you should be able to do it in the slice case too...
This is a very minor nitpick, I do agree that compared to many other languages golang is very easy to mentally parse, at the expense of some expressivity at times.
Whenever I code in it, which these days is pretty much all the time, I do miss some conveniences from other languages (generics, list comprehensions, ...) but of course every language has its tradeoffs, I most certainly miss golang's strengths every time I have to write some C or python.
IMO interfaces are the Achilles' heel of Golang. Another thing that has been bugging me about them is that you cannot specify which interface you're implementing in the code (beside from comments). I think that is so because Go hates circular imports. If you had to import a file to be able to use an interface, you'd soon run into compile errors due to circular dependencies.
And as there is no way to immediately see where some interface is declared and if/which interface is implemented, you have to rely on comments and resort to manual search to find out more. That may be easy in smaller codebases or worthy of effort in important codebases such as Go standard library. But all an all this ambiguity does not fit with Go's discipline to prevent human errors by making everything uniform and explicit as possible.
Just for the sake of providing an example, let's look at time.go under https://golang.org/src/time/time.go, we can see in the comments of several functions that the programmer states a particular interface is implemented. Line 1112 of the aforementioned file is as follows:
Now where is encoding.BinaryMarshaler declared? There is a package called encoding, maybe there? But the package has many many files, where would I find where BinaryMarshaler is defined? I'd have to resort to manual search. Now imagine that this is not an interface in the standard library, but rather an interface in some mediocre codebase that you're handed for the first time...
Tl;dr All I'm saying is that runtime errors due to empty interfaces are not the only flaw of Golang. Interfaces as implemented in Go could in some cases present a serious threat to code structure.
I actually experienced the opposite. Interfaces in go work so well, partly because you can create an interface which automatically gets satisfied by existing structures. For example I often use a requestDoer interface which only has the http clients "Do" method.
I'm using Goland so I have jump to interface from any structure implementing it. So that's another one that gets solved by tooling.
The structurally typed interfaces are actually my favorite part about Go probably.
How do you jump to the interface from any structure implementing it? Beside from comments there is no immediate way to which interface/interfaces a structure is implementing.
Maybe Gogland solves this by keeping records on all interfaces, and checking all structures whether they satisfy them. If that's the case, this is definitely solved by tooling.
It would be nice to add at least the options to explicitly state that type X should implement interface I, and have the compiler barf / emit an error if X does not implement Y.
Like I said before, this has not been enough of a problem to really bother me, but I would be quite happy if Go fixed this.
> I think that is so because Go hates circular imports.
Well it could be but I don't think so --- "duck typing" when it comes to interface{}s has been a goal from early on, quite simply. The very idea of interfaces as "a set of func signatures" preclude the necessity to verbosely spell out "struct Foo implements BinaryMarshaler" and/or "func (_ Foo) MarshalBinary implements BinaryMarshaler.MarshalBinary"!
In the same vain, you don't even need to declare interface TYPES, you can accept/pass/return "inlined" interface{MethodSig(argtype)rettype} anywhere if you want. Not the most ergonomic choice in practice, but the possibility hints at this underlying interfaces-are-duck-typed-method-sets paradigm at work here.
> take for example interface slices, you start by learning that you can assign any type to interface{}, so intuitively you'd think that you could assign any type slice to []interface{} but you find out soon enough that doesn't work
Why do you expect this to be the case? Just because you can assign a char to an int in C, you wouldn't expect to be able to assign a char pointer to an int pointer. Similar for C++ and std::vector<char> vs std::vector<int>.
It's the same and probably even more relevant with void∗/int∗ (assignable) vs. void∗∗/int∗∗ (not assignable). You can assign an int∗∗ to a void∗, though, just as you can assign an []int to an interface{} in Go.
Also, even as an absolute greenfield programmer with no experience in other system languages, if you really think it through, you will come to the conclusion that it mustn't be possible to assign arbitrary slice values to an []interface{} variable, given the way slices work.
> intuitively you'd think that you could assign any type slice to []interface{} but you find out soon enough that doesn't work
I've been writing Go for upwards of six years, professionally for three, and I have never tried that. I can't think of the last time I used interface{}...
I generally consider anything using interface{} crap code someone who doesn't know the language wrote, probably coming from a loosely typed language.
> I generally consider anything using interface{} crap code someone who doesn't know the language wrote, probably coming from a loosely typed language.
Disagree. There's ample usage of interface{} that you need to type-switch on in parts of the stdlib that deal with the AST and reflection or various other areas. You might also UnmarshalJSON a structure where some field can contain any one of a number of possible sub-structures. Once we're talking about 1-2 dozens of possible sub-structures, you'll have that field be an interface{} and type-switch on it --- rather than having 1-2 dozen pointer fields that will all-but-1 be nil and having to if-else through them all.
Go does have brilliant simplicity! It's the simplicity of Pascal I used in middle school, and of Modula-2 I used on my freshman year, with basically the same syntax. I'm glad Go revived a number of good ideas from Pascal / Modula / Oberon.
For writing toy programs to relax (can relate), I personally prefer Python, or maybe a Scheme. While also being simple at the core concepts, they have much more expressive power, an easier way to combine simple things into complex things. They pay for that by higher resource consumption, of course. I do fondly remember a PDP-11 Pascal compiler reporting "15 KB used" after compiling my program; you can't get a Python process with these constraints. But by now we have _vastly_ more computing power.
Go has human-sized simplicity. Programming language complexity is limited by human cognitive limitations, and Go has a small cognitive load.
This makes it feel like a very intuitive language.
That doesn't mean it is an intuitive language - just that it feels like one for a specific class of problems.
Personally I enjoy using it for the same reasons as everyone else. But... I'm also aware it's quite an old-fashioned language, with some bumps under the floorboards where useful things were hammered down to make them go away, instead of being fully solved.
> I write in my free time to relax (sounds weird, I know)
Not weird at all. I do that myself. I believe the relaxation is in pure creativity, the wonder of exploring something novel and the absence of any pressure or stress due to timelines or deadlines. You're also exercising both right and left sides of the brain.
I suspect there are many many more who do the same.
Huh. That are exactly the reasons I would give if I had to explain that hobby to somebody. ;-)
Feels kind of good to know that I am not the only one doing this.
When it goes really well, programming is akin to what I think meditation must be like. But explaining that to people who have never programmed themselves is hard.
> But something about is very compatible with the way my mind works.
Yes. For me it is channels. After nearly 20 years of UNIX'ish systems, pipes are a mental abstraction that I do not have to think about any more. And channels fit right in, they feel much closer to a how a pipe is used on the cli than a pipe or socketpair ever did in code.
For example a range loop over a closed channel is, for me, piping things to xargs. It's easy to understand, reason and conceptualize because it feels familiar.
I know other people meet a similar purose by knitting or solving crossword puzzles, but explaining to them the trancendetal nature of their favorite pastime is quite difficult.
(I'm not sure if GitHub sorting is broken, but the same issue has more +1s than the top +1d issue as per that sort mode as well)
Generics has the most experience reports in Go 2 proposal. Maybe this can be categorized and excluded as a vocal minority, but above data doesn't seem to.
I'm afraid not all votes are (and should be) considered equal by the project maintainers. A great number of casual Go users might want feature XYZ. OTOH a couple dozen of large Go projects, with combined millions MLOCs and hundreds of millions of end users, may have a different list of priorities, and these priorities might be catered first.
Also, let's not forget that Go is created at Google, and definitely Google's internal projects, likely large-scale by both line count and users served metrics, must take priority.
Frankly, there is a number of reasonably nice languages that do provide generics, good / great type systems, good / great package management, decent async features, and quite decent performance: Rust, Nim, Crystal, ... all the way down to Haskell. Go is not competing with them at their strongest features. Instead, it's in the sweet spot of simplicity, fast build times, reasonable hygiene, and trivial deployment. It's the "getting job done" mentality which worked so well for PHP and Perl back in the day. There is a considerable demand for that.
>I'm afraid not all votes are (and should be) considered equal by the project maintainers. A great number of casual Go users might want feature XYZ. OTOH a couple dozen of large Go projects, with combined millions MLOCs and hundreds of millions of end users, may have a different list of priorities, and these priorities might be catered first.
Many large users writing in company and project blogs have mentioned lack of Generics as a pain point, so that distinction doesn't go very far.
>Also, let's not forget that Go is created at Google, and definitely Google's internal projects, likely large-scale by both line count and users served metrics, must take priority.
Go was not created through some official decree to create a Google language, nor was it mandated to be used or to have some timeline to convert other stuff to it, etc.
It's just a project started by some Go people with the idea "let's examine what a language appropriate for Google's programming would be" -- but using their personal ideas of what Google style programming would need, not something requested, researched etc by Google as an organization. In other words it started as a glorified 20% project, and today it's not any more official in Google than C++ or Java is.
I suspect that Rob Pike's and Russ Cox's work is paid for by Google.
But even if it were not, I suspect that heavyweight users like Google do have a weighty say, just because of the sheer amount of practical experience they have with developing and running software using the language. Just like Mozilla, I suppose, do have a say in the development of Rust.
We’ve actually worked a lot to ensure that Mozilla can’t dictate what happens with Rust; we run a consensus based process, and while Mozilla has the largest single group of people involved in leadership, it’s still a very stark minority.
As a huge production user, Mozilla’s needs are still important to us, but we think of Rust as an open source project Mozilla contributes to, not a Mozilla project per se.
>I suspect that Rob Pike's and Russ Cox's work is paid for by Google.
They are. The distinction I'm trying to make is they have not been tasked to "create an official Google language for Google scale projects", nor what the language officially adopted as "THE" google language the way e.g. Kotlin was officially adopted as a language for Android programming.
Some googlers (Pike and Cox among them) sat and designed a language that they thought would be good for Google scale programming (according to their ideas and experience) and they went and implemented it. Google paid their salaries the whole time, but at least at first, not for the purpose of creating such a language. After the language is out it paid, and even added more people, to keep it going --it gives good PR, and can be used internally here and there, so that's worth it to them.
It's just not "the Google language" in the way Obj-C/Swift are the Apple languages and C#/VC++ is the MS one (the company heavily investing in them, explicitly asking them to be built, suggesting their use everywhere, etc. For Google it's more of a side project than what e.g. Lars Bak worked on.
It's the biggest issue, but that doesn't mean the majority of Go developers support it. 1734 people reacted to the generics issue, most in favor, but there are many more Go developers than that.
But the people who read the proposal and reacted to it may be the vocal minority who are concerned about generics. The majority may not spend their time reading or reacting to proposals they don't care about.
>Also, let's not forget that Go is created at Google, and definitely Google's internal projects, likely large-scale by both line count and users served metrics, must take priority.
And many more want Generics without having responded to the issue.
Issues are representative as a sampling, not absolute numbers.
Unless you do random sampling, you still can't conclude anything one way or the other about whether it's a majority of users. Perhaps something like Google Surveys could be used to settle this?
> Issues are representative as a sampling, not absolute numbers.
My point was that issues are not a reliable sample of the Go community as a whole. They're a self-selected population of Go developers who cared enough about generics to click on a Github proposal on the subject and react to it. I'll say it again:
>> The majority may not spend their time reading or reacting to proposals they don't care about.
Maybe the majority do want generics, but the Github issue is poor evidence. A random sample of Go developers would be much better, and then we wouldn't have to make up things like "And many more want Generics without having responded to the issue."
I accidentally re-pasted the same quote from nine_k I've answered to higher up the thread.
Meant to quote this from you: "1734 people reacted to the generics issue, most in favor, but there are many more Go developers than that".
>*
My point was that issues are not a reliable sample of the Go community as a whole. They're a self-selected population of Go developers who cared enough about generics to click on a Github proposal on the subject and react to it.*
And my point is that I don't think this is the case. I don't know either way, but there's no reason to assume people caring to vote in the issue are necessarily not representative -- like I don't think that for any other project/issue combo on github.
In any case, even if they aren't representative of existing heavy Golang users, who cares about them? The language is still niche. There are tons more programmers to come to the language than those that already are using already.
So I would very much pay attention to what those not yet using it but caring enough to vote have to say about it.
And then there are many Go users, who don't really care for generics, or like me, think generics could be nice, but could also make the language more complex than desirable.
If someone came up with a practical, clean, working proposal for Go - then absolutely. But so far I've only seen complaints and proposals where the conclusion starts with "this proposal will not be adopted" - mostly because it's somehow flawed.
It is a vocal minority in the (probably correct) assumption that the majority of Go programmers are not part of that GitHub issue, nor any other group asking for Generics.
It should be obvious to folks on HN why a Github issue that may go completely unseen by a large swath of the go community, or +1's which can be gamed/trolled, should not be taken as a valid measure.
What the developers want is for people to submit _actual_ problems that generics would solve, with examples. Because there are more than a few way to do it and they want to pick the right one.
That's easy. Concurrent access to maps is unsafe. https://golang.org/pkg/sync/#Map should be a drop-in replacement but due to the lack of generics it can't present a compatible or typesafe API.
I've run into concurrency problems using maps while writing an irc client. It was really frustrating for me, because it did not happen everytime I ran the program, but rather rarely. I had chosen Go for it's memory-safety and easy concurrency. But I felt like I could not vouch for my program's safety any more. I did not know about sync/map at the time, had I known, I would probably have used it.
I think Golang has a fondness for magical (to me) special language constructs. For example if you want to use the RPC modules, you have to write your functions a certain way. Or as I've explained in a comment above, if you want to use an interface you just have to implement all it's properties. Or you can't have generics, but we have a special language construct called maps, where you kind of can mimick genericks but it is not safe and may break.
Golang is a great language, but I think it expects all its users to think very rigidly in the same way their designers do. Basically, you have to explore the language's deficiencies yourself, read the designers' explanations, dive into the intrinsics of the language, understand it, make yourself believe and move on. I sometimes half-jokingly feel like Golang follows the principle of most obedience.
Are the complaints about lack of generics really a minority thing? Writing separate functions to sort different types just strikes me as ridiculous.
EDIT: My original tone was a bit nasty in retrospect. Did a little research and while I still am on the generics side, the current situation seems at least workable for a good number of use cases.
My concern is less about writing a bunch of separate functions, but rather the way this hampers the abstractions that are available to you everywhere in the language. You don't have map or fold! And that's only the very tip of the iceberg.
Yeah, I wish go had more functional stuff, like .map(), .filter(), etc.
On the other hand I enjoy that when I pickup a package someone wrote there is not a bunch of different meta-programming using templates.
Generics sometimes lead people (including myself) to over-engineer solutions... usually because we want as much compile safety as possible. But at what cost in complexity.
I groan every time I have to implement the 15th type-specific loop implementation of what I'd do with a single map or fold in Ruby/Elm/Haskell/JS/TS/Crystal/every other language I use. It's not a lot of effort but it's a lot of mess and cruft.
Apparently, the project leaders see it as a lower-priority (though not unimportant) issue. Looking at the adoption figures, they seem to be right.
I personally stay away from Go due to lack of generics and other expressiveness issues. People who have to work with it write code generators on top of the compiler, because the compiler team won't include it into the language. (I can see how it's not an easy thing to do; Russ Cox wrote a nice piece about it.)
Java prior to version 5 had the very same problem. It was wildly popular nevertheless. It took Java 8 years to gain support for generics, though, about as long as it took Go to not yet obtain them. (By that time, Java was widely considered the Cobol of 21st century; I think Go must be successfully stealing that crown now.)
I have had the need to sort data rarely enough that it has not been a real problem to me (most of the time that data came out of a relational database that did the sorting for me).
But when I did have the need to sort stuff, I have found it annoying. Even C has a more convenient solution for this problem. (Admittedly, Go's sort.Sort can work for data structures other than arrays/slices.)
Like I said, it has not been a sufficiently large problem to really bother me, but it is not a pretty solution, IMHO.
Of course, one might argue that the creators of Go knew that sorting things was not such a common problem for their target audience, and thus they made the trade off to make sorting suck in return for overall simplicity; Go's type system makes it practically impossible to implement a type-generic sorting function like C's qsort(3) without sacrificing performance or making the language more complex.
So maybe that is one of the trade offs we have to make. I still wish for a better solution, even if that may be impossible without turning Go into another C++. And if you like C++, that is totally fine, it has a number of very big advantages. But then you do not need Go to become another C++ if you have the original right there; and even if you wanted to get away from C++ without giving up the benefits it offers, D looks like a more promising alternative.
But this is something you often need to do for any non-trivial type, even with generics. There's a reason that C++ std::sort<> takes a comparator, or why Rust requires you to implement the 'Ord' interface.
Again, it's a little clunkier in Go, but not unusably so.
You need to define how to order things in other languages (though not in languages where you can automatically derive implementations like Haskell or languages with built-in polymorphic comparison like OCaml) but you don't need to define how to swap elements over and over.
A little boilerplate hasn't killed anyone. If you're talking about "100s of different Enterprise Business Objects (TM) structs", something is probably off in the overall program design and/or code-gen should probably be introduced regardless of the 'sort' (and related typically-generics use-cases) question, as that sounds like something to be largely derived from pre-existing schemas of some sort..
Yes, it is minor. Go has interfaces, so you just write a Comparer interface and sorter that takes two Comparers and then anything implementing Comparer is sortable.
*Note: author has written basically nothing in Go and only has a passing familiarity.
So you define the Comparer interface with a Compare method. What does it look like?
If I have a struct X, then I might write it as:
interface Comparer {
Compare(other X)
}
But wait, now I have to define a new interface for every type since "Compare" takes the type X in its signature so it doesn't work for type Y... If only I could define an interface that was for an unknown type T and then Compare was for that.
But that's exactly what generics are.
You know how Java has .equals? Go doesn't have an equivalent concept. Test code is neigh unreadable because there is no generic way to compare two structs of the same type. This is a similar problem.
I am admittedly thinking of an interface from how they work in Java, so I may be making incorrect assumptions about how they work in Go, but wouldn't I have to do that anyway? In what sense can the compiler work out how I want to compare my types? If I hand it some vec3s, for instance, does it know I want them sorted by x value, or by length?
By defining an interface that assures the compiler each type has a compare(x) method, I can write a generic sort function that takes any two comparable objects and sorts them based on whatever the class of those objects decided was the ordering criteria.
If your `Comparer` is an element type (as opposed to Go's `sort.Interface` abstraction over collections), this is going to be horribly unperformant. Suppose your `Foo` type implements `Comparer`, and you have a large `[]Foo`. Then you'll first have to iterate over each element in `[]Foo` and add it to your `[]Comparer`, probably with an allocation per element. Then each invocation of `foo.Compare()` is going to be indirect as well (in other words, an interface lookup and a function call, i.e. no inlining). Mind you, this is still faster than many languages, but it's not the kind of performance people expect from Go.
The primary differences (I think) are that everything in Java is already a pointer, so there isn't an extra alloc to make an interface. The same is true in Go if your comparables are all pointers. Probably the more significant difference is that Java has a JIT compiler, which may be able to inline all of those calls to Compare(). This is largely why Go's `sort` package abstracts over the collection, not over the element.
I don't think there is a technical reason. It's probably not something they'll do for a while because it probably requires some significant reorganization of the compiler and making a passably fast implementation is probably quite hard (the Go community places a premium on fast compiler times, rightly or wrongly). Also they seem to value predictable optimizations. Not sure how keen they would be to add an optimization that works until a seemingly inoccuous code change prevents the compiler from guaranteeing that all elements in the slice have the same concrete type, making performance (probably) significantly worse.
Again, I don't necessarily agree with the Go team; I'm just guessing the are some of the concerns they're weighing.
It's not about sorting, it's about every single abstract operation on a collection or other type of container. You either have to use primitives, or reimplement everything yourself on each new type. Most people just use primitives. This proliferation of primitives is what I actually dislike about reading and writing code in Go, but it's caused by not having a way to define abstract data types that work well.
I’m not sure about you, but it feels very reasonable to me. It’s not anywhere near the top of my list of pains that I feel when working with Go everyday.
interfaces are a poor substitute for generics, but that's by design. the minute they add a reasonable implementation of generics Go will begin to metastatize into something unrecognizable as people will no longer be bound by the limitations of the already existing core generic types. the core developers really do not want that to happen.
it's a highly opinionated language and for some reason some people enjoy writing mountains of for loops so that their code can remain "simple" as they produce a maze of twisty funcs all alike. i mostly see a for loop or two per func with a map and some slices repeating the same patterns over and over. that's the way the language designers want it.
i work with a lot of go programmers and they are all fine and good programmers. that makes it even harder to understand why they enjoy all the boilerplate and even defend it.
Ignorant here (I've never done database work):
What's wrong with asking the database to do the sorting for you? (assuming you do work that involves a database. Perhaps this assumption itself is the problem?)
I understand why that would make you uncomfortable. ;-)
But to be honest, more than 50% of the cases where I needed data to be sorted in a certain way, that data came out of a relational database, and adding an ORDER BY-clause to the SQL query was so much easier than doing it myself.
I do agree with the second part of your comment in that Go has a certain type of application where it really shines. But that type of application is not uncommon, and when you hit the sweet spot, it really shines.
care to enlighten me? if you have an index you could get the sort for free, also you never pull all results, so why would you pull all, just to sort, on the web I would love to work more in ram, but since you assume a server will crash, you need to save the actual state in the db.
I didn't down-vote, but what jumps out to me is the assumption that you'll generally be pulling data from a database. Yeah, that's almost certainly true in a web-application, but it's not going to be nearly as universal in a systems-programming niche which is where Go appears to be excelling (as perceived by someone outside of the Go community).
So ease of implementing sorting in your core language without making assumptions about the operating environment of code written in that language sounds like a reasonable demand.
> by far most of the Go developers still prefer to use the language without generics
I've been writing Go regularly for at least 5 years, and my guess is that at most 60% of Go developers prefer no generics. I also think that number is climbing.
Not the OP, but here are my 2¢. Introducing generics in a language implies a deep revision of the type system, which risks to become much more complex to understand. Moreover, this might have a bad impact on compilation performance (compare C++ compilation times with Go compilation times and you'll understand what I mean).
To be clear I prefer generics. The cases for why other Go developers don't want generics seem to mostly fall on "it will change the language / encourage people to write stupidly complex code" to "it's too hard to implement". The first one is valid--Go's constraints give it this nice property that there is (more or less) one obvious way to write most programs, and that will go away with generics; however, I think that cost is worth the gain. The other criticism seems like a cop-out; the Go team has far and away more than enough talent to slap generics onto the language.
Not OP, and this is only one aspect to consider, but it's from experience - when generics were added to C# the language became nicer to use, but it caused a lot of churn and added complexity. The base libraries have the original non-generic classes as well as newer generic ones.
As I've written above, there's huge potential to be the language that doesn't churn, when just about everything else does these days.
yes, but those of us who would desperately like to use Go because there are vanishingly few GC languages that compile to native exe AOT, won't because of no generics. So it is a bit of a tautology.
How useful they are depends greatly on what you are doing, and programmers do a great many different things. For many kinds of library development, they can save you massive amounts of time and code, and/or lead to much better performance vs the workarounds available.
I wonder how many people who prefer Go without generics are coming from C++ templates, or Java generics, vs C# or F# generics.
To be clear, I agree with you. I was just responding to the OP's claim that the Go community overwhelmingly rejects generics with my perception that at most 60% of the community feels that way. I am very much in favor of generics.
What amazes me most about Go is that they managed to successfully pitch a C-like systems language that does not promote use of shared libraries. About six years passed before they added an option to create them.
Were there many complaints about the absence of shared libraries originally?
Whenever I have mentioned the benefits of compiling C programs as static binaries and that in some cases with todays hardware one can have enough memory that shared libraries may not be necessary in order to conserve memory (one of the original goals of shared libraries), I have encountered substantial resistance to the idea. Perhaps some folks are interested in shared libraries for reasons other than memory conservation.
The deployment time benefits of static binaries far outweigh the resource saving benefits of dynamic linking. I'm speaking from experience with C++. There is also a latency boost for high CPU intensive processes.
The Go community mostly deploys network server daemons to its own datacenter infrastructure. C and systems programming in general also target end-user devices, where it isn't the programmer's memory to spend.
The open-source community already has a mature ecosystem for dealing with shared libraries via package managers and distro maintainers. In a company's proprietary backend it's almost certainly cheaper to throw memory at the problem than to spend engineer time crafting and maintaining a coherent/compatible set of packages for its various backend services.
In my experience, shared libraries can simplify deployment as well; say you’ve got a 100 programs using the same library, and you need to change the implementation. With a shared library you only need to build and deploy one artifact, you don’t need to relink and redeploy 100 programs.
> while paying much less attention to the vocal minority's complaints
That's debatable. Those who can't stand the language don't bother using it,therefore don't bother complaining about it.
My point is Go biggest critics have already abandoned the language long ago.
Go reminds me Rails. It had a huge success 8 years ago but since all other languages have caught up when it comes to RAD webdev. The JVM and others will eventually catch up.
i don't think you can "catch up" with go, because it has aimed at being minimalist. catching up by removing major features to a language isn't something i've ever witnessed.
You can absolutely catch up with the concurrency model, on the single binary deployment, and a other features. Go isn't "minimalist", that's false, go look at the reflect package, it's complex as hell.
> ... while paying much less attention to the vocal minority's complaints (generics, package system, etc).
It is much more difficult not to add a feature than to add it, and the ability to reject even important feature requests is very important for the survival of a system in long term. This observation is formulated in "Getting Real" (https://gettingreal.37signals.com) as "Forget Feature Requests" and "Start With No".
People want generics because they think every language should have generics.
What they don't know is the exact cases where the lack of generics causes a real-world problem for them. This is what the developers are requesting at the moment, in order to possibly bring generics to Go 2.
Every statically-typed language needs generics to allow a function to be used with more than one type. Otherwise you throw away most of the type info and end up with the same problems dynamically-typed languages have.
This is exactly my feeling. I don't want generics if I have to pay anything for them in build time, or if it limits go/ast or go/types. A lack of generics has been a pain point a couple of times in my current project (beyond what very simple code generators can provide), but crazy fast build times have been a massive benefit.
Imagine how much wailing and gnashing of teeth there will be if Go 2 delivers generics but with triple the build time and +50% binary size... if you thought the minority was vocal now, wait til you see that!
Like pretty much every successful programming language.
Most of them have one original goal, and a core philosophy to go with that goal and that guides the compromises.
Take C for instance : its original goal was to write UNIX. Because of this, it has to be low level, portable, and efficient. These are the important things and that's why people use C. Features important for writing UNIX stuff get priority, this ensure consistency.
Go is Google's language, made to be effective at doing what Google does. And its direction ensure that people who follow Google's way of coding are happy. There are other options for those who don't like it.
Go is an example of how to be extremely successful by inventing Unix and then going obscure for 30 years doing stuff like Inferno and Plan 9, and making a comeback when Google shows up.
Go is woefully missing some really key features, which you encounter when tuning it for high performance. My list of grievances:
- Dep handling was never considered. Makes sense given Google's monorepo but thats not how the world works.
- Stdlib just loosely wraps posix features with many C flags copied verbatim. These APIs are old and could use a refresh but Go never bothered.
- No easy way to construct arenas/pools. Once you go down this route you have a great headache of releasing in the right places. The GC doesn't cut it, you need pools sometimes
- Debugging basically doesn't work on some OSes. No easy way to attach gdb and see what's happening. Doubly so if you use Cgo
- Similarly, Go doesnt bother to hide the differences of different OSes. Up to you as the programmer. Again not surprising for Google's all Linux world. If everything is Linux then OS difference doesnt matter. But even Python does a better job here.
- Logging is poorly thought out as evidenced by multitude of third-party log packages. Anemic compiler means you can't get verbose logging without paying a performance penalty.
- No RAII. Defers are a lazy attempt at this, but they're not even close to being as good as RAII. This is probably the biggest point where you realize Go can't dethrone C++
- Tricky Close() semantics force you to architect entire program around who will close() things at end of their lifetime. Lots of terrible hacks ensue when people build something that works but realize close ownership is ambiguous and rightfully don't want to rebuild it all
- Channels don't have a good way to deal with errors. You're forced to come up with an ad hoc solution to deal with your graph of goroutines/channels when one node errors
- No supervision tree. Erlang existed far before Go but they didn't learn from this key feature. But it would greatly enhance Go to have it
- Hacky reflection semantics that cause subtle runtime bugs when a JSON struct field's name starts with a lowercase letter. And of course, there are no generics, the larger issue here.
I was hopeful that Go would fix some of these things before it went 1.0 and locked in its syntax. Sadly that didn't happen as it was likely already locked in at Google. Go is ultimately kind of brain dead, useful for some very particular features but not so compelling that it can replace any other language.
> - Stdlib just loosely wraps posix features with many C flags copied verbatim. These APIs are old and could use a refresh but Go never bothered.
> - Similarly, Go doesnt bother to hide the differences of different OSes.
I disagree with these. Go runtime/stdlib is architected to work around many many POSIX headaches and design bugs, hiding them completely from programmers, and to be fully portable. For instance:
* Concurrency is completely redesigned (goroutines).
* Signal handling is redesigned and doesn't cause bugs when interacting with concurrency.
* Forking/Exec'ing is redesigned not to cause fd leaks in subprocesses (all file descriptors are marked as O_CLOEXEC, in a race-free way), nor have races while interacting with concurrency
* Sockets are exposed through a higher-level API (Dialer/Listener).
* epoll is not exposed but transparently used by a single thread to handle all supported file descriptors without wasting OS threads, to improve performance
In fact, I think the only thing that is pretty much low-level is os.File and filesystem access in general, which tends to expose lower-level details.
Having experienced a situation where I had to call select() directly, I have to completely disagree. If you run into situations like this, you need the syscall escape hatch. It's really painful to get this working, and it interacts poorly with goroutines.
Sockets are actually another good example. If you need to tweak then at all, you get to use the same old horrendous POSIX names for everything. No name aliases?
This would suggest using an actor-like model, which is pretty senseless for single-machine usage. It's totally understandable for Erlang, where the VM spans multiple machines, so everything is unreliable, but in Go, I take it for granted, that my goroutines won't "just crash".
I'm not sure, but I think this also ends up being good for modelling interactions with good performance, as you know where you do network calls, where you have reliability, and where you have instant local calls. Though not to say that Erlang applications aren't blazingly fast too.
> It's totally understandable for Erlang, where the VM spans multiple machines, so everything is unreliable, but in Go, I take it for granted, that my goroutines won't "just crash".
Supervision tree gives a structure to put whole subsystems somewhere,
including their boot initialization and shutdown (not just restarting it at
crash), allows to spawn workers while keeping usual logging/crash monitoring
for free, gives a way to inspect the system at runtime (much easier
debugging), and allows to run several different applications in the same VM
space, which is a flexible way of combining code. I can, for instance, run
a CouchDB instance with bolted on my own HTTP server that exposes monitoring
data or an administrative interface. It cannot be done in Go (or any other
language, barring maybe Lisps) without modifying source code.
And no, none of this "spans multiple machines", it all works and helps on just
one single node. Maybe except for debugger, where you spawn a shell node from
which you operate.
And yes, your goroutines will "just crash" in situations you haven't expected,
as every non-trivial daemon experiences crashes on transient bugs. Though in
Erlang transient bugs don't bring the whole daemon down. Saying `my goroutines
won't "just crash"' means `my code and code I use as libraries doesn't have
bugs, ever'.
So every unrelated request that happened to be currently processed is thrown
away because of a rare corner case, and with no way to intercept shutdown and
save state, flush buffers, or anything. Yes, totally right granulation.
Not to mention that you have just introduced a very complicated piece of
software to run a single daemon.
Idiomatic Erlang involves handling only errors that are expected (read: part of the program’s specification), and crashing on any other kind of error. This keeps the error handling out of the business logic and makes the app code simpler and more reliable.
For instance, you can’t forget to close a socket, release a lock, or log the actor state and stacktrace, since these things are handled by the VM and supervisors when an actor dies. These are nice properties to have even in a single-node system if you’re aiming for high reliability.
I do agree though that a supervision tree doesn’t make as much sense in Go, since the VM does not provide the cleanup guarantees and strong process isolation that make the Erlang model really workable.
Wow! My network daemon at work, written in Lua (which uses Lua coroutines for processing, which are similar enough to Go routines/channels for this comparison) has a supervision tree. It can log the unexpected death of Lua coroutines and keep going, which is nice for the unexpected bit of input [1]. It's also helpful when testing new code as unexpected errors are logged (stack dump, so the location of the crash is reported) in development/QA.
[1] Yeah yeah, all input should be well specified. Could you please inform the vendors that their programs are spewing unspecified crap at me? Thanks.
I'm not disagreeing with you (I've previously criticized Go here) but I think it's helpful to think of Go as a more "modern" C. A lot of the warts you see here (Cludgey POSIX interfaces, no smart scheduling around channel error handling, bad reflection semantics, etc.) are the same as you'd find in C.
Except, it's not a compelling replacement of C. The type system doesn't give me good assurances about program behavior at compile time. Go inserts itself as a heavy unwanted additional layer for cases where I want C. Especially considering that now my program will have GC pauses.
> - No supervision tree. Erlang existed far before Go but they didn't learn from this key feature. But it would greatly enhance Go to have it
This is the one thing in your list that I don't recognise. What is a supervision tree? Can you (or another Erlang programmer) point to your favourite reference?
A supervision tree would be a tree of goroutines where a "master" (supervisor) gets notified when children die (normally and more importantly abnormally).
In Go, if a goroutine panics for some reason it just dies on its own and the world does not know unless you have some sort of ping service or you specifically send a notification from a defer.
In Erlang, if a process faults and it has a supervisor, the supervisor gets a message that one of its children died with some additional metadata, it can then log the issue, restart a child, … This also led to an interesting "let it crash" philosophy (since Erlang processes have isolated private heaps a process dying also cleans up any potential data corruption in the process's private state, this is often considered a feature, but at its core it mostly avoids processes dying unnoticed.
Incidentally, supervision is actually the result of two individual features: linking and trapping. Linking means that when one process dies with an abnormal status (any reason other than regular process exit), all process linked to it will also die (the EXIT signal gets propagated through the link), which is repeated until all linked processes are killed, trapping lets a process handle the incoming exit signal in code rather than automatically suicide
Finally, because supervisors and supervision trees are so common the Erlang standard library provides generic behaviours which let developers declaratively set up which strategies they want in their supervisor, the maximum restart frequency (beyond which the supervisor consider the system's broken and shuts down), ...
> In Go, if a goroutine panics for some reason it just dies on its own and the world does not know unless you have some sort of ping service
If a goroutine panics and is not recovered, it not only dies but tears down the whole process. This seems to be the more appropriate approach for a language with pervasive shared state (locks, etc.). Erlang is quite different here, as you say.
Restarting or otherwise dealing with the exiting process is best left to the service manager that started it.
The best ecosystem out there? Since when? I'd say that Java and Python have huge, wonderful and full ecosystems. Golang doesn't even come close to that, yet. Furthermore, Golang doesn't even have a community standard (or several standards) dependency manager.
Python's packaging is a nightmare of half-baked, incompatible approaches that puts the lie to the famous "Zen of Python" that "There should be one-- and preferably only one --obvious way to do it."
I hear people praise the Java ecosystem so often, but when I use it I cannot replicate that.
There is nothing like Django in Java land? Instead you are sent into a confusing mess of interface standards like JPA and implementations (hibernate, ebeans, ...) and little guidance how to choose.
Equivalent of Django would be something like Spring Boot, Play Framework etc.
It's quite common that some of these frameworks rely on community infrastructure that has several competing implementations, like JPA, but the point of a tool like Boot is that they picked all the implementations for you and documented it all centrally.
We tried Play. It is not close to Django. At least with Java. It might be better if you use Scala.
For example, one thing which seems to be worse across the board is database evolutions. Django will autogenerate an evolution in Python for you [0] and if necessary you can adapt the code. In Java land you have to write SQL by hand [1] afaik? If SQL is not enough, I don't know what to do at all.
In the process of hacking on my PureScript2Golang "transcompiler", I've realized that under the hood ADTs, also known as "tagged unions", will likely more often than not be represented exactly as just that: a "tag flag" and a data pointer. (Of course if all of an ADT's ctors are nullary, an enumish int will do the trick just as well --- I doubt this gains anything over interface{}-boxed 0-size struct type-defs though in reality.)
Now the natural "box"/container for this tag+content pattern in Go is the `interface{}` that can hold as 'content' any of your "constructors" (whether it be a struct or type-alias or prim type), plus your 'tag' pattern-matchings translate to Go's type-assertions. You see this also whenever ADTs are translated to JS or OOP languages, in some manner the "type" tag is carried along to be able to switch-case on.
Anyway, you can do poor-man's ADTs as per above today. The catch is that you get fewer compile-time checks (won't check for pattern exhaustiveness in your switch-cases, or illegal/impossible/invalid (as per your custom "dumb" ADT layouts) type assertions).
But anyone who's done some linked lists or devised parser ASTs in Go has written "ADTs" in this manner --- whether by intention or inadvertently =)
Personally, I don't care about Generics now that I'm aiming to generate low-level Go code for PureScript's parametric polymorphism and higher-kinded types system.. --- expressive beyond the old-school oop/imperative "generics" band-aid ;)
> That I can add interfaces implementations to structs I don’t own
Wait, did I miss out on some new feature only recently introduced? The way I parse your above statement, it sounds like you could define methods for receiver types imported from other packages, is that what you meant? Or did you simply have classical workarounds in mind, such as type-aliasing or set-the-"method"-as-a-struct's-func-typed-field?
Current approach: http://github.com/metaleap/gonad-corefn (to be renamed later) --- heavily in-flux, even more slow-going pretty-soon-now (long-term freelance gig coming up), but I'm committed to keep committing =) --- won't be polished in terms of docs/tests/better-commit-msgs etc until reaching a certain stage of completeness. Also about 1/3rd of the code can be ditched at some point, coming from earlier stages --- only will become fully apparent which 1/3 at a later stage of maturity as well though.
In Go’s philosophy most problems are not exceptional. File can’t be opened? This is to be expected on systems with file permissions. The common pattern is for functions to return an error type as last return argument (Go supports returning multiple arguments). Programmers can then handle the problem right then and there if the error is not nil. This approach also helps to keep control flow straight forward. The happy path flows from top to bottom. If an error occurs, it is typically handled in an if-block. There are no nested exception blocks or exception blocks that follow each other and do things far removed from the exception’s origin in the code. As a result Go code looks very clean and is easy to follow.
This isn't true at all depending on your work. I program software that has to work alongside other programs on busy file systems...failing to read/write a file is not someone else's problem...it needs to be handled, logged, there is potentially a retry and/or notification.
For exceptional, unforeseen situations you do have exceptions, aka "panic".
For signaling error conditions that the caller has to expect and handle, you have the `result, err = func(...)` idiom, and a compiler that would warn you if you forget to use the value of `err`.
Go does not warn you if you forget to handle an error. It only does so if the function in question also returned a value that you're using. There exist important functions that don't return non-error values and report errors that you very much would like to avoid dropping on the floor: os.Chdir() for example.
While this is technically true, I've considered `errcheck` to be standard tooling for what feels like forever now, and it does exactly this; make sure you're checking your errors.
I’ve read the first article. It was eye opening, cause I am writing a bot right now and using exceptions to control input flow. Made me think about my design.
That said, it does not argue against exceptions. So, I am not sure what your argument is.
Exceptions are bad outside the "your computer just started burning" cases, but Go has replaced them with something even worse, "multiple return values".
So instead of some imagined return of "int or throw Exception" you now have "(int, error)", which basically means that the result of a function call can be any of these four options:
- ( value, no error)
- (no value, error)
- ( value, error)
- (no value, no error)
And due to the lack of Generics you can't abstract over your error handling.
And due to the lack of proper ADTs you can't even properly model "value OR error" manually.
The last case is rarely seen in Go (at least not in the standard library).
Accepting for the moment that Go has no exceptions and and error returns are the way to go, the first two cases make sense.
The third case ( value, error) is actually useful in several scenarios. For instance, considering you're writing bytes to a stream that fail partway. The value is the number of bytes return so far and the error is the error that was encountered. In fact, this is the signature that the ubiquitous io.Writer's Write method uses.
If all you had was single return values (aka "int or throw Exception"), how would you model the io.Writer?
The last two options are not idiomatic Go.
(zero, err) and (nonZero, nil) are.
So the error check is almost always a simple `if err != nil {return err}`.
Exceptions are one of those things which are really nifty when programming in the small. However, they can create outsize problems when programming in the large.
I thought this as for a long time. Then I used go for awhile and I think I like the go way better. It solves the issues I had with execptionless languages without all the noise.
With exceptions you most often just let them trickle up the call chain, which is the same thing you often do with err, just return it.
And the returning the err works much better when doing async. Cross thread exceptions are a PITA and you are basically back to just returning an error obj.
On the whole I agree that I prefer Go's explicit error returns over other languages implicit "alt-return paradigm" aka exceptions. But you're really well advised here to run a couple of linters occasionally that inform about ignored/unchecked/non-passed-on errors, it can happen all too easily while "rodeo-coding" prototypes/MVPs
I have tried Go and worked with languages without exceptions in the past. The problem is the amount of error handling code I was writing time and time again. It might only be a case of returning an error but that is one more thing you need to think about instead of concentrating on the the domain.
I wonder how the presence of ADTs and absence of generic functions would mesh together.
With ADTs, using a sum type instead of the `result, err = func(...)`, would be an obvious thing to do. But the next thing you'd consider would be `bind` / `>>=`.
I'm not sure why the next thing you'd consider is bind. It seems like plenty of languages with ADTs and generics still either don't have it available in their libraries, or use it extremely lightly, in spite of being able to implement it.
I'm referring specifically to the error-handling situation.
If you replace the two-value assignment with an `Either`, it feels very natural to also eschew the constant `if (err != nil) return err` lines, and adopt a continuation-passing style, the way you do with Futures in JavaScript. You could even consider some syntactic sugar around it, like the `?` operator of C# and Kotlin, or a full-on Haskell-like do-notation.
Again, it doesn't seem like this is something that many communities do, in spite of having languages that support it. The '?' operators in the languages you cited, as far as I recall, are specific to null, and don't work on generic option types.
Go has (inferior) exceptions, they are called panics. error as value is not a feature of the language, it's a convention.
> 1. Probably the best ecosystem out there.
it will when it gets an acceptable PDF library, better enterprise integration (SOAP and co) and an actual debugger (and no Delve isn't good enough given how it often fails at basic debugging).
> every single cloud company has critical components of their cloud infrastructure implemented in Go
And this is how the "network effect" propagates. As the customers of cloud services also begin to experiment in Golang. And discover the holistic ecosystem of distributed systems packages.
Even Blizzard with its massive C++ codebase is gaining converts
That source just mentioned a few popular open source projects and provided no sources citing actual cloud providers using it in their backend infrastructure.
Just to accentuate some of the neat things you can do with Go, watch: 'Can you write an OS Kernel in Go?' from the Golang UK Conf.
https://www.youtube.com/watch?v=8T3VxGrrJwc
They're not important. The author admitted she was just shitposting. It was an amusing thread, but rife with misinformation. If someone wanted valid criticisms of Go, I would not send them there.
I use golang since 2010. My only complaint is that they dropped gxui. So I use golang mainly for cli utilities. The other options (I do not like javascript) are ill-maintained. For me it is more important than generics.
For me Go is amazing, is my first language where I don't need a virtual machine or interpreter to compile to machine code, C++ is OK but not for day to day web.
Changing something then having to wait 40 seconds for java to recompile drive me crazy, also same for tests. Yes would love to have a package system, but is coming.
Eclipse or IntelliJ IDEs will compile while you code (like C#). Depends on the project setup and dependencies. But I've found that this works for most development until I need to produce an artifact. Then I run maven which does a lot more than just compile code and usually is longer than 40s.
Yeah, having to run a whole IDE just to get incremental compilation is pretty annoying tho. Perhaps I should just bite the Java bullet and try to use IntelliJ.
You don't normally use Maven to compile every change when doing Java projects. Your IDE will incrementally compile what's needed based on importing the Maven config. You use Maven for tasks like packaging, linting, building JavaDocs etc.
I did worked with spring boot and intelijava, and I timed for a decent size project it was over 40 seconds, but I believe a lot of it was Intelijava compiling, anyway, in go is instant.
This would have a positive impact on Google, too, as the more server chip competition there is, the cheaper it will be for Google to buy those chips for its cloud services.
Could it be a strange mindset of our time that we look to language ecosystems to provide this? What if we could manage dependencies without being locked into language?
Conda goes some way. Think of something like virtualenv, but not specific to python. I would be happier with something that had fewer features than conda, but where critical features were easier. Example: setting up a package server should be trivial.
I think we do not yet have the git of dependency systems, with the gravity to draw in the community and for people to regard it a solved problem. Maybe the most efficient route would be: find a way to get Linus pissed off about it.
How about Nix, could it be the 'virtualenv, but not specific to python' you mentioned? I've never used it, but from what I understand that would be an appropriate description of Nix.
I don't know why people are so upset about this. Go standardizes enough things about imports and package structure that you can completely solve dependency management in literally a shell script: https://github.com/holocm/golangvend (I'm using this productively for the Go apps that I develop at work).
Glide is great - it supported the most important feature I cared about, which was aliasing for forks where I had to fix bugs. "Standard" matters less because there's also no centrally-controlled repository of packages, unlike npm/maven/pip