Precisely, and this is one area where go fails completely. The features don't interact well at all!
Tuple returns are everywhere, but there are no tools to operate on them without manually splitting the halves, checking conditionally if one of them exists, and returning something different based on each possibility. Cue the noise of subtly-different variants of `if res, err := nil; err != nil` in every function.
Imports were just paths to repositories. Everything was assumed to just pull from the tip of the branch, and this was considered to be just fine because nobody should ever break backwards compatibility. They've spent years trying to dig themselves out from under this one.
Everything should have a default zero value. Including pointers. So now we go back to having to do manual `nil` checking for anything that might receive a nil. But thanks to the magic of interfaces, if you call a function that returns a nil interface pointer, it will directly fail a nil comparison check! This is completely bonkers.
Go has implicit implementation of interfaces which makes exhaustive checking of case statements impossible. So you type-switch and hope nobody adds a new interface implementation. So you helpfully get strong typing everywhere except for the places you're most likely to actually mess something up.
Go genuinely feels like a language where multiple people each had their pet idea of some feature to add, but nobody ever came together to work on how to actually make those features work in concert with one-another. That anyone could feel the opposite is absolutely incomprehensible to me.
> but I've often heard Go described as taking the programming language advances over the last 50 years and throwing them away.
That's by design, right? Go is very opinionated. They looked at other languages that they hated, e.g., C++/Java and didn't want to replicate them. But then adding their own mistakes along the way.
The brand new mistake that surprised me was that nil does not always equal nil. So just checking for nil is not enough sometimes, one has to "cast" as nil to the type you're expecting. And goland doesn't catch it. C++/Java/Python/C, null always equals null. But not in golang. shrug
The primary issue I have is that go doesn't make it very easy to write unit tests. You have to use interfaces everywhere just to inject your mocks.
I feel like that's the big mistake they made. Any new language needs to make it super easy to write unit tests without forcing major design decisions that affects development.
> I just can't go back to Go with nil pointers and lack of decent enums/ADTs/pattern matching either.
Go is simply a badly designed language where the idea of "simplicity" has been maligned and proven bad ideas likes nil/null, exceptions and such have been introduced in a seemingly modern language. One would think that decades of Java, Javascript, etc. code blowing up because of this issues would teach someone something but seems that is not always the case.
> Go handles it shitty, like the past five decades of computer science and PL design never happened.
This has generally been my complaint with Go. Go is... fine. It offers a lot of features that seem revolutionary if you've never seen them before, but mostly feel like someone took the best parts of every language, and then implemented the least interesting aspects of those features.
- Interfaces are like typeclasses, but without actually offering any sort of higher-kinded polymorphism.
- Goroutines are kind of like Erlang processes, but without any notion of supervision and with shared mutable state.
- The error handling feels like it's trying to emulate an Either monad, but in a way that doesn't actually allow for the compositional properties that make an Either monad useful.
I know that Go's goal was never to innovate on language design, but I feel like it ignored a lot of low-hanging fruit in modern language design.
Obviously you can do worse than coding in Go, but for everything Go is good at, there's likely an even better tool for the job. I'm not convinced that there's any domain where Go is the best choice available, but it's definitely average at most jobs, and maybe that's where it finds its niche.
Edited: I had missed the word "higher-kinded" in front of polymorphism. Thanks donatj!
> I do still have a weird feeling of getting back to the stone age whenever i get a nil pointer panic though. i wish go devs could figure out a way of fixing those last quirks without impacting the rest too much.
Unfortunately fixing the "billion dollar mistake" as a retrofit is pretty much impossible without breaking backward compatibility. I see what they were trying for with nil and zero values, but IMO they should have introduced a "result" and "option" special type (or probably more Go-like would be separating nullable and non-nullable types via '?' like Dart did). If they did it like Dart, they could intro the concept without breaking backwards compatibility, wait X years until mostly adopted everywhere, and THEN break backwards compatibility by making it mandatory.
> I can't explain it, but I find the language infuriating. It somehow manages to be less expressive, more verbose and more straight up boring than all the other options.
I sort of get it. I think people fixate too much on the for loops and the `if err != nil` boilerplate, but there's definitely some validity with respect to "how to properly annotate errors" and emulating enums (in the Rust sense of the word) is pretty error prone and it still doesn't get you exhaustive pattern matching.
The stuff I like about Go:
* Single static, native binaries - being able to just send someone an executable is pretty nice, not needing to make sure people have the right libs and runtime installed is phenomenal.
* Great tooling. I love that the Go tool takes a list of dependencies. Unlike Gradle/CMake/etc I don't have to script my own build tool in a crumby/slow imperative DSL.
* Compilation speed. Go compiles really fast.
* Small language, easy learning curve. Any programmer can read just about any Go project with very little experience. Any Go programmer can jump into any other Go project and be productive immediately (no extensive configuration of IDE or build tools or anything like that). You can also write surprisingly abstract code without reaching for generics, but you will likely have to change your programming habits.
* Value types done right. Most other mainstream languages don't have them, and the ones that do very often implement them poorly (e.g., IIRC in C# you can define a type as a struct or a class and only structs can be value types, and classes are more idiomatic).
* Go eschews inheritance and prefers data-oriented programming over object-oriented programming (although OOP is poorly defined and some people think it means "anything with dot-method syntax").
* I'm just more productive in Go than any other language
Stuff I don't like about Go:
* No enums
* I wish there were fewer tradeoffs between abstraction and performance
* Needs a better system for annotating errors
* Doesn't run on embedded systems (although I've heard good things about TinyGo)
* Doesn't make me feel as clever (this is a nice property for professional software development, but for hobbies not so much)
On balance, I think Go is the better language, but I can also understand why it chafes people. Different strokes. :)
> The simplicity of Go did not result in simpler systems.
I echo this 100%. I worked at an employer who heavily used golang. The resulting projects were honestly a mess, yet they pushed through.
What's ironic, is that they ended up reinventing the wheel on so many different things, including DI and an entire application framework. Millions of dollars spent writing and maintaining these libraries, where Java already had them ages ago.
As you point out, the modeling capability of golang is extremely subpar. It results in verbose code that is very sparse, lots of code to implement something that would have taken a few lines in a language like Java and C#, let alone something more dense like Scala.
The way interfaces are handled in golang makes it very annoying to try to find out which types implement said interface. It puts a lot of pressure on the IDE to search the entire code base, and you end up with types you don't even care about. It clearly shows that the golang authors did not have IDEs in mind when writing the language, which is just absurd as it's supposedly a language designed for "programming in the large". Anyone who used "goland" knows what I'm talking about. Try refactoring a type, and have the IDE scan the entire code base to look for comment strings, which it has to because golang has no notion of doc strings like Java or C#.
Add features like records, pattern matching, enums (the amount of code that had to be written to implement something to emulate enums was just absurd and frustrating to deal with, not to mention error prone).
The concurrency aspect of golang is decent (though it will be even better in Java). Other than that, the language doesn't really have anything going for it other than having a large brand name backing it.
> The lesson Go seems to have learned is that, since C++ and Java burned their fingers, clearly fire is too dangerous for humans.
I think that's a little bit unfair, since Go introduces many powerful ideas not (traditionally) available in Java or C++: namely first class concurrency and functional primitives. Its handling of typing, the ability to define types not necessarily based on structs, the excellent design of interfaces are other examples. Go is an extremely powerful and expressive language that opens up the doors for programming in new paradigms, while making it easy to maintain readability and simplicity.
Fair point with the nil issue, I think that's one of Go's other weaknesses. But it does make up for that with its excellent error handling paradigm.
> Go was built explicitly to solve the real everyday problems of working in a large code base, largely driven directly by experience of several industry titans
This claim keeps getting repeated with absolutely nothing to back it up, other than repeating what the golang authors claimed.
I work on a large golang code base, and it's quite horrid. The IDE experience is terrible. It's hard to navigate the code base, due to things like types unintentionally implementing interfaces because they happen to match on some of their function declarations. No proper visibility rules (only "public" and "package private"). interface{} everywhere because of no generics (I'm aware that's being worked on). Single character variable names are popular because the golang authors write code that way and it's seen as "smart". No reserved "this" or "self" keyword to declare methods means that every type will have its own single letter self variable which you have to look up every time. Error handling is terrible, and I've seen many insidious cases of mishandled errors. Null pointers are still a thing. The list goes on.
Edit: not to mention things like the language is against quick prototyping (heavy friction due to things like unnused imports or variables, and a very verbose language), as well as the lack of mature libraries such as JDBI or Dapper.
> but realize it's just because go hasn't been widely used in GUI development
There are a lot more reasons than that.
For one, Go has awful support for any kind of generic programming. The closest thing Go has to proper generic support is the interface{} type, which is awful. It's basically the same type safety as a 100% dynamically typed language (i.e. almost none), but with a much uglier syntax.
A related issue is that Go isn't very extensible. For example, Go's iteration features only work on builtin types. You can't iterate over trees, graphs, sets, or any other non-builtin type.
For me, the only good things that Go offers are decent threading primitives and good standard library support. Nothing else excels, and some things are downright bad.
> the poster made many points that could be used to argue that go does not promote developer productivity.
I think go promotes maintainability rather than developer productivity. IMO, go authors favored ease of code reading to the expense of code writers. That being said...
> no option types aka nil access, no enforcement of error checking aka no result types, no enums, no conditional compilation, no iterators, no immutables, etc...
No enforcement of error is not true, you have to explicitely ignore an error if you want to. You have almost the same problem with rust, where you can unwrap things that can fail, thus explicitely ignoring potential errors (granted, the program will then panic).
No conditional compilation is not true either, you can, eg, put at the top of your file build tags:
// +build !linux
This file won't be compiled on linux (you supposedly have another version of that file for linux systems).
No immutables: can be a problem, const is very limited indeed in go.
Nil access, yes, although contrarily to C/C++, you can't dereference nil without having the program panic. AFAICT I never had them happen in production, when I have a panic it's because of an off-by-one error in a slice, usually (but then I'd have the same problem with `safe` languages like rust).
I'd be glad to have enums. Go's workaround is safer than C's enums, but not by much.
> it's bad to keep adding all features to all languages
I understand the reasoning behind this take - a lot of modern languages have wound up somewhat bloated, with features that are either non-orthogonal to other features and break the 'one way to do it' principle (Scala comes to mind), or with features that seem tacked-on and feel alien to the language (the latest versions of python come to mind). I understand, in theory, why go tries to buck this trend.
The problem is that in bucking this trend, go has (at least historically) not evolved enough to fix some of its own core issues. And it does have some big issues - nils, zero values, and non-thread safe primitives come to mind for me. I'm all for being thoughtful about introducing changes and making sure those changes are idiomatic and don't violate the 'ethos of go'. But avoiding making those changes in the name of 'keeping it simple' never made sense to me in cases where programmers are shooting themselves in the feet and already not having a simple time with your language.
> The compiler does a whole load of checking which can (and therefore should) be automated, which in many other languages is left to the developer. Go code tends to have the property that if it compiles, it probably works as designed.
As someone who works with Go daily, these statements are both very untrue. Claiming "if it compiles it works" applies to Go code makes my eye twitchy.
For instance, type assertions have no exhaustivity checker. Interface{} and liberal usage of it throws type safety out the window.
> the package management is lovely,
Go doesn't have package management per SE, just vendoring.
> I worked at an employer who heavily used golang ... they ended up reinventing the wheel on so many different things, including DI and an entire application framework.
Sounds like they were trying to force the language to be something it's not. DI frameworks (I think what you mean) are basically incoherent in Go, dependency injection is naturally modeled with interfaces. In fact any inversion-of-control style "framework" is a mismatch to the language, it (intentionally) lacks the features that frameworks rely on to deliver benefits that outweigh their costs.
> The way interfaces are handled in golang makes it very annoying to try to find out which types implement said interface.
The desire to do this reflects a misunderstanding of interfaces.
> And yet it still feels like a language out of the 60s or 70s.
Lisp is from that era and is far more poweful.
> To me, Go feels like the programming language equivalent of insisting that everyone communicate just by grunting
This is fair. I mean, Go is outright _primitive_. The most notorious things about the language itself: are Goroutines, channels and the interfaces. This is why there is so much discussion around, for instance, Generics. Noone really "wants" Generics. Instead, they just need to not repeat themselves. There are very few ways to work around issues in the language itself.
Another issue is that, if a library implements something in a way that doesn't suit you, or you need to add extra behavior, you are out of luck. Say you want to add distributed tracing to code that's not owned by you (even if writting in your company). You'll have to track down people, you cannot "annotate" your way as other languages can.
That said, the fact that it is mind-numbly dumb can be a feature. Everything is damn obvious. Error handlers? Staring at you in the face like the sore thumb they are supposed to be. You can't even get too fancy with 'design patterns', just go write the function you need to solve your problem and stop reading that gang of four book.
They are fixing the Go modules mess, which is one of the last pieces of the ecosystem. No "coding standards" document – it is whatever go fmt and the go compiler says it is. Lots of trivial things are errors (like unused variables), which helps keep the codebase sane. The ecosystem and toolset are great (I feel like Rust learned from Go and improved on it).
Go has to be compared with alternatives. Rust? Awesome language, but slightly different niche with a far higher learning curve. Java? Please. C or C++? They are the opposite of simple (C is a larger language, but with far more baggage, don't get me started on C++). I can't include Python and ilk as their purpose is completely different, although for things like backends both could be used.
What else could we use to write Docker or Kubernetes, that would be more expressive, and still learnable in a weekend?
> There must be a golden balance here
I would argue that Go has not nailed it completely, but it is in the ballpark.
> Many of the "old" ways of thinking do not apply to Go
Whatever. Just because the language is effectively broken doesn't make practices in other languages irrelevant. And it's funny when you talk about the "old" ways of thinking in language that basically promotes C styles errors and bad macros through go gen.
When I say the language is broken, look at Go type system, then look at how Go reflection makes the language look a dynamically typed one. It would have been smarter to tone down the reflection and make it unnecessary with a smarter type system. If it is there then it is because it was necessary for Go designers. If it had not been, then Go would not need that insane reflection.
Now Go back to your Go code and remember all tricks you need to learn when working with arrays :
> I wish there were better languages that had such nice tooling
Have you considered that the reason why go has such great tooling is precisely because of its desperate grip on simplicity and non-configurability -- the very things you might call upon as why it's "bad".
I should say, Rob Pike didn't put in the language the features that would have helped alleviate the pain of writing 50,000 times the exact same statement littering Go code bases like a plague. Go errors are purely a convention that is no better than C style error code returns. Not good enough in whatever year Go was designed. There are solutions to that issue that are much more composable than that (options, sum types...).
People come to Go for performance and concurrency (although context is another one of these tacked on horrors) and they leave the language because of the crazy ceremonial boilerplate it incurs.
> At least any argument why Go is not productive, why its productivity is an illusion.
- generic code only via go generate and interface {}
- verbose error handling
- the way dependencies are handled
- no language support for functional paradigms
- nil pointer semantics in interfaces
- very thin type system
Go would be a great language in the mid-90's, nowadays given the features adopted in the mainstream it feels too little.
For me, the positive thing is that it may lure developers that would use C to use Go instead, and in the process discover that their use case is perfectly doable with a memory safe language.
> (E.g. you'll want a "match" operator. And then that means you need all statements work as expressions. And you'll have to change how zero values work, which are pervasive throughout the language.)
To me, that just sounds like the designers really painted themselves into a corner. I have yet to run into a situation where everything-is-an-expression feels like a problem. Am I missing something?
> At some point if you really want Haskell you should just use Haskell. Or Rust. And then you will find out that those languages have problems too, and you will understand that engineering is a question of tradeoffs, not of feature checklists like this blog post.
I think it's clear that Go has contributed to the conversation. In really accessible ways, it made some powerful points on the benefits of auto-formatting, fast compilation, static binaries, and so forth. But there's just so much missing of the highly productive points made by other languages, and there seems to be so little energy in the community to progress on those points. In that sense, it feels to me like Go is a bit of a dead end.
Precisely, and this is one area where go fails completely. The features don't interact well at all!
Tuple returns are everywhere, but there are no tools to operate on them without manually splitting the halves, checking conditionally if one of them exists, and returning something different based on each possibility. Cue the noise of subtly-different variants of `if res, err := nil; err != nil` in every function.
Imports were just paths to repositories. Everything was assumed to just pull from the tip of the branch, and this was considered to be just fine because nobody should ever break backwards compatibility. They've spent years trying to dig themselves out from under this one.
Everything should have a default zero value. Including pointers. So now we go back to having to do manual `nil` checking for anything that might receive a nil. But thanks to the magic of interfaces, if you call a function that returns a nil interface pointer, it will directly fail a nil comparison check! This is completely bonkers.
Go has implicit implementation of interfaces which makes exhaustive checking of case statements impossible. So you type-switch and hope nobody adds a new interface implementation. So you helpfully get strong typing everywhere except for the places you're most likely to actually mess something up.
Go genuinely feels like a language where multiple people each had their pet idea of some feature to add, but nobody ever came together to work on how to actually make those features work in concert with one-another. That anyone could feel the opposite is absolutely incomprehensible to me.
reply