I started programming in the 80s, and Go feels modern even compared to the mainstream languages of this era (mainly BASIC, Pascal and C).
It also feels modern compared to the first versions of Java in the 90s.
You don't like Go and I respect that, but Go definitely reflects its time, the 2000s, with its syntax, automatic type deduction, GC, gofmt, interfaces, goroutines, closures, built-in HTTP, built-in testing framework, etc.
more like the 60s, with a primitive type system, no metaprogramming, no interactive development, etc.
Go's answer to metaprogramming is source code generation. There are many problems with C++ but the existence of the template system isn't one.
Sure, Boost is full of metaprogramming insanity, but with today's C++ standard library it's not necessary to use Boost.
10 years ago template heavy code in C++ did suck, because we had slow compilers, dumb build systems, 7200 rpm HDDs and a need to use Boost to augment the bare bones standard library.
I'll be the first to admit that C++ has decades of cruft and design errors. But the metaprogramming works pretty well now. C++'s current problems are (IMO) weird undefined behavior, e.g. the distinction between POD and non-POD types; having both pointers and references, the ancient exception system, and in general a lack of tools for enforcing memory safety.
What we should talk about instead: hygienic macros in Scheme/Rust, AST-level modification in Nim, dependent types, etc. I think metaprogramming is an aspect of programming that should be explored more, and is definitely useful for reducing boilerplate.
Go is not always elegant, it's not always the right tool, and sometimes frustrating - but it's incredibly good at enabling better productivity than the alternatives in this niche. Sample size of one, this is my opinion, and other disclaimers apply.
Racket for general programming (It really is the best for people learning to program and also great for making fun programs that is super easy to install on multiple of computers)
R for statistics especially since I found the functional backbone of the language.
Go for backend work.
I find myself thinking the exact opposite. When you want to do something quick and dirty JavaScript or Ruby are my go to. When you want to do something right, do it in Haskell or Rust.
The middle of the road options I find are worse for anything.
Unless you want to hire big teams. Java, C#, Go are great for that.
But I find with more complex type systems and more expressive languages (which applies to both Haskell and Rust) I spend too much time thinking of which way in going to code something, what abstractions I'm going to use. Then I spend too long trying to make the compiler happy for decreasing marginal returns in reducing bugs. Then the compiler takes too long every time I want to run it. On top of all that, the tools are subpar. I like Rust, I find it so well thought out and elegant, but I still reach for Go to get things done.
All of that makes me considerably less productive. In Go I just use loops, slices, structs, and interfaces. There is usually only one obvious way to do it. It compiles right away, and I get on with my life.
It's not as pretty to look at, probably more lines of code, but it takes so much less time.
On larger teams having simple, consistently styled code is an understated advantage for code review and understanding the system (which together are probably 3/4 of the job.)
In my view, Rust has a good chance of gradually replacing C++, because C++ devs don't shy away from using a large, complex language, and they can appreciate what the borrow checker does for them.
I use TypeScript for the browser and React native, because that is much better done in JavaScript land.
For short scripts I use Python.
Everything else I use Go.
Note that type errors in typescript are actually just warnings and you can ignore them. I never do, but you can.
I wasn't aware that typescript type errors were only warnings. Every webpack/typescript project I've used must have had the typecheck set to error, leading me to believe typescript was some draconian type checker akin to Rust or Haskell.
That's interesting, because to me, Go accomplishes this fairly well. Generics, operator overloading, and custom 'for' loops make it easier to write "clever" code with surprising behavior. Go's design is not elegant like Lisp or Haskell; there are some warts and special cases that exist for pragmatic reasons. But it manages to be simple and effective, which is sadly a rare thing in the modern language landscape, where the mentality seems to be "more features = better language." 90% of Go's value is in what it takes away, rather than what it adds.
timothycrosley: It doesn't do generics!!! Aaargh!!
+ gofmt feels way out of date. These days clang-format (c++), prettier (typescript), black (python), scalafmt (scala) take care of wrapping/unwrapping lines such as function definitions or function calls. They basically cover all formatting needs so you never have to manually format anything.
+ Scope of element in for-range loop isn't right, so capturing that scope in a lambda does the wrong thing with no warning.
+ Encourages use of indexes; which is error prone, most modern languages allow writing most code without needing indexes using map/filter/reduce or comprehensions.
+ No help from type-system for use of pointer without nil check.
+ Very easy to get nils in places one would hope to be able to prohibit them in. EG Using pointer as a poor man's unique_ptr<> means that I also get optional<> symantics (without the type checking) when I don't want or expect such. Also allows for aliasing when I don't want or expect such.
+ Difference between '=' and ':=' is silly, especially since ':=' can be used to reassign values. Even more frustrating that ':=' creates shadowing in nested scopes, so doesn't always do what one would expect it would do, such as accidentally creating a shadowed 'err' that doesn't get checked.
+ if/switch should be allowed to be expressions, allowing much safer single-expression initialization of variables, rather then requiring default initialization and mutation, which is much easier to get wrong.
I think any new language should be designed around options instead of nils. F#, Rust, Zig show different ways to do this, and often any performance penalty can be compiled away.
if/switch being expressions is a simple and helpful idea, languages should allow this.
using map/filter/reduce as the idiomatic way to do things I am less sure about. This can come in handy but also would add a lot of complexity to Go, and in most languages these have a performance penalty.
its important to remember that not all programmers are interested in languages, they just want to get their project done. So being able to hop into a code base and have low cognitive overhead, because there are no mysterious features they have to learn, having quick compile times, and explicit semantics can be really helpful there. That can save you more time than typing less because of generics and meta programming sometimes.
The loop semantics are always the same, the element variable is a copy of the value at the current index. Yes that means that if the value is a pointer, the copy of that pointer can be used to modify the pointed data. This is something a good Go programmer should understand well because this behavior goes way beyond for loops. For example functions - func (t Thing) vs func (t * Thing) - with the pointer version the body of the function can modify the pointed data. Side-effects! Just like the for loop.
> gofmt feels way out of date.
I like to think of it as stable. The biggest strength of gofmt is how universal it is. Everyone uses it and all code out there looks the same. There's a growing collection of Go code out there. If gofmt would keep changing its style, then all the already published code would no longer match the standard. Thus, I think there needs to be a real strong reason to modify anything about it.
Arrays/slices/maps/pointers: Go abstracts over the inherent safety-limitations in ways that do not make much sense. They may make sense from a blend of safety- and performance perspective.
Index usage: Go allows to loop over elements instead of indexes and also provides the correct range of indexes in for-loops. More functional expressions would mystify execution, while Go is more WYSIWYG of languages.
Shadowing: Yes bad, but also bad to have many layers of deep scopes for this to become problematic. Best practices of Error-variable has problems.
Initialization in if/switch: Go being a niche lower level ("system") language, it's closer to the actual physical layer. A good idea not to do too much in the same expression/line, making it easier to read and making correct assumptions.
Indexes are the default though, you have to explicitly ignore them if you want to use the values directly.
More importantly, there are several simple operations that simply require indexes: most error prone is trying to create a pointer to an element in a slice. The natural, high level way of doing that would be
pointers := []*element{}
for _,value := rang elements {
pointers = append(pointers, &value)
}
Which looks very nice, but does the completely wrong thing. You absolutely must use the index version of you want to do this. Same would be true if you were to capture the value in a closure.Sometimes (most of the time, actually) I want exactly that. Please, take a look at cryptography libraries and try to implement them without using indexes. Horrifying. Try to create a 3-dimensional array in Erlang, for example, and try to work with it![1] No thank you. I do like my arrays and indexes.
[1] I do use and like Erlang for stuff where I do not have to use arrays though.
gofmt is the best formatter out there because it is opinionated. The fact that people constantly tune their formatter (if there is one) in other languages makes it a nightmare to read different codebases. As someone who used to read code for a living, Golang is a pure joy to read, you always feel like you're in the same codebase.
> + No help from type-system for use of pointer without nil check.
I do wish they had an Option type
> + Difference between '=' and ':=' is silly, especially since ':=' can be used to reassign values. Even more frustrating that ':=' creates shadowing in nested scopes, so doesn't always do what one would expect it would do, such as accidentally creating a shadowed 'err' that doesn't get checked.
I too am not a fan of shadowing via :=
> + if/switch should be allowed to be expressions, allowing much safer single-expression initialization of variables, rather then requiring default initialization and mutation, which is much easier to get wrong.
I would advocate for match statements instead.
I had the same experience as a Java dev back when Java8 came out. To me, it felt great. To people using functional languages which this update ported to Java, it looked poorly.
It is hard to understand go's criticism if you have not experienced languages leveraging modern datastructures such as, for instance, union types/pattern matching.
But if you have, you really feel like the language lacks something.
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.
My learning experience with Go ended when I happened upon this. Unused variables aren't even worth a warning, and yet it is absolutely impossible to get around them in Go.
Maybe it's just my style, but I like to start out code with methods and variables in rough outline, and then build it from there. If a language makes that impossible well...there are other languages. Also, I didn't want to learn what other doozies Go had in store if they made that kind of stupid nanny issue a categorical imperative.
Done.
I mean I wouldn't object to it being a bit more forgiving in some instances, as long as it's as anal as can be when you commit some work and / or share it with others.
Of course, it's an open source project so in theory the unused variable check can be disabled.
Please elaborate.
> I can't include Python and ilk as their purpose is completely different, although for things like backends both could be used.
Yet, many real world sysadmin programs formerly written in Python are now written in Go. Go's compilation to a single statically linked binary, and tool support for easy cross-compilation makes it popular for these usecases. And when many people say "Go is blazingly fast!" they compare it to Python.
> What else could we use to write Docker or Kubernetes
Borg, I think, was written in Java at Google.
What happens if the 'fanfare' doesn't stop anytime soon? Will you just keep watching from afar that ever growing crowd of grunting and stinky subhumans?
You're just describing me in the morning.
Everything works out-of-the-box, it has tooling for formatting, building, getting dependencies, generating docs, and so on.
Its standard library is the most feature-complete and the most impressive standard library I've seen in any programming language.
Code written in Golang is always a pleasure to read.
And what are the frameworks you consider to be the most advanced in decades?
I haven't looked at Go because...my corporation doesn't really use Go. :shrugs:. I don't understand why anyone would get personally excited about it unless it gives them a breath of fresh air from the worse languages they have to deal with, like as an officially-sanctioned second language where they work, or they just don't look at languages very much.