I get that that is Go's design philosophy, but IMO a little syntactic sugar would be nice.
I get that that is Go's design philosophy, but IMO a little syntactic sugar would be nice.
I believe GO success is partly because in recent years, we have many new specialized engineers that programming isn’t their main focus (DevOps, Data Scientists, SREs) and naturally they are looking for tooling with shorter learning curve.
Also when you consider how many developers have a hard time to learn GIT after so many years, you realize why companies need something as simple as GO comparing to Rust, or Java comparing to Scala for instance.
Speak for yourself maybe, but I appreciate Go's overall style. You also mention Rust and Scala in your post. If that is to be the future of language syntax, I should quit.
I mean it's gotten better but take Javascript where just a few years ago fundamental branching & execution order and might be provided by some library and it wasn't always the same one (thinking especially of various competing promise libraries) or friggin' imports might look different. Or Rails where you might have a codebase come across your laptop that's in some version you haven't used recently and using a bunch of libraries you're not used to and now half the symbols in a given file aren't defined there or anywhere else that Grep can find and it's unclear where they even come from, so it's off to the docs for a bunch of abandoned gems to get any sense of what's even going on.
Go mostly avoids that sort of deeply unproductive junk. It does achieve that by not providing certain things, plus static typing (thank god for TypeScript, finally some sanity). I'm OK with that. I don't use it as my daily driver but I'm never even a little sad to drop into or contribute to a Go codebase, and I don't need a huge margin of uncertainty on the question "how fast can you get up to speed with this?" for Go code, which I can't say of just about any other language, sight-unseen.
I will say that a rust-style error propagation operator would cut verbosity even further. (Though that is harder to do in go with their choice of multiple return over sum types.)
Still, try/catch seems far more verbose for common error handling strategies to me. And I'd rather see an acknowledgement that something can fail as close to that thing as possible, rather than hunting thorough the stack to find some global try/catch block that silently swallows the error in order to cut down on verbosity.
If only Java's particular implementation hadn't ruined the public opinion on checked exceptions...
This comes up all the time on HN and I've personally landed on preferring Java's checked/unchecked system from anything else I've seen. I get that sometimes you should handle your errors in the small, like Go's errors or Rust's Maybe (or Try?) styles might prefer, but I hate not having any exceptions options to "let things crash".
Rust and golang do both also contain a `panic` that allows you to just "let things crash". But by being separate from the standard methods of error handling, they tend to be used in actual exceptional circumstances.
I'm not a fan of exceptions in general, but a system that had exclusively checked exceptions, but didn't require such verbosity would be nice. Perhaps an annotation that says a method can throw, and any method without that annotation cannot throw?
Yes! If expert code and novice code look pretty much the same, that is great!
I think rather than futzing around with the language, go frees you to think harder about the problem domain.
Better architecture, design decisions, problem domain modelling, documentation / communication, core data structure and algorithm selection, api design - these are the things that distinguish expert from novice output, not statement level expressiveness.
There's obviously a line to be drawn somewhere! - an expert can write "good" code and develop useful systems in GW Basic, but few would voluntarily choose to in 2019. Really though, the runtime limits the problems you can solve in GW Basic more than the language syntax.
I think this is why go focuses a lot on the runtime - low latency gc, concurrency, etc - at the high end of programming ability the limits on what problems you can solve in a language (without dropping down / calling out) are dictated by the power of your runtime, not language syntax.
As a data point, when I think about programmers with the most impactful software outputs (rather than talks, essays etc) it's hard to overlook the C programmers. Git could have been written in any number of languages but fairly straightforward C is enough to get the job done.
The "expert" work in git exists at a much higher level than the source code.
Or maybe it's an indication that I should spend less time playing code golf and more time building what I actually set out to build.
I'd imagine an expert could write a better, performant database than a novice and the fact that can be achieved in language that will effectively communicate to a novice is arguably a good thing.
Especially if I'm not rushed, I have an annoying tendency to try for the most elegant, succinct, idiomatic or general way to program a solution, and given a sufficiently expressive language I then fall victim to analysis paralysis.
By giving me so few options, Go removes a major cognitive load. Very often there's a single obvious way to get something done and I'm not tempted to try anything fancy.
When pretty much anything can implement any interface, and one can only tell whether an interface is fully implemented by close examination of a whole source file (rather than reading a simple implements keyword), the cognitive load is far greater.
The lack of an implements is not just a matter of "less syntax fever!" but one that facilitates other language design choices: in particular static compile time duck-typing. Among the "everybody knows or knows-about" class of languages this is pretty fresh. You can go back and forth on the point of this (or the value of the trade-offs) but it's pretty key to Go's flavor. As a Java geek, I'm pretty familiar with the way you'd prefer and I like it quite a bit too, but once I'd tried it, I've really missed Go's flavor more than a handful of times.
Static duck-typing has some great downstream ecosystem effects. For one, it really facilitates smaller, tight interfaces. If my code uses a type A from another package B, and A has 20 functions that in my code only uses 3 of, I can create an interface on my end with just the 3. Then any testing or any alternative implementations apply only to the minimal interface. If that alternative implementation is by another author, it can interop with my code without importing type A package B. Obviously you don't need this all the time but it's killer when you do.
I don't write compilers or other "highbrow" Computer Science-heavy programs. I most frequently find myself writing interface software, data converters, text extractors, near-system utilities. Lots of the kind of things some people might write in C or Perl. As a result, my usage of data types, even structs, is pretty light. Turns out I can do most of my work with the built-in types: integers, characters, strings, arrays and hashes. I rarely even need/use interfaces!
Data types don't play a big role in my work. Your mileage may vary, obviously.
I find golang simplicity liberating, especially now with GO111MODULE.
No need for stuff like node’s package.json, no maven’s project.xml (I have no problem with maven), no sbt, small native binaries (this is where golang, in my head, beats erlang), simple type system. Instead of focusing on designing the ins and outs of the architecture, it allows me to focus on the flow, like erlang! The structure, design, come later, with proper unit and integration tests.
I love it, it is so simple to switch from r&d mode into production grade system development.
I'm pretty happy that I can use Go to do these things, but you are absolutely right that without the syntactic sugar it gets quite messy to read!
Lambda expressions - sure you can do them - but it'd be nice if you also had the syntax we came to know from other langs. (->, =>)
Are you properly using the Template pattern, and creating middlewares that factor out repeated bits?
There is some stuff that doesn't work for very well, but it does work for many things and it's easy to miss. (Go doesn't have many tools. You must learn to use the ones you have to the fullest. If you do, the remaining gaps are much, much smaller than commonly supposed.)
I have found that there is a bit of a misconception in the Go community that there's something special about http.Handler and that you need lots of special code to deal with it. I've seen the misconception that "middleware" is something special and that you can only do it to http.Handlers a lot, but a "middleware" is just "a decorator applied to http.Handler". You can decorate any interface value the same way. There's also a misconception that "routers" are something special, like, they have their own type or something. But they aren't. They're just http.Handlers that can examine a request and figure out which other http.Handler to call. One thing I do a lot is wrap my top-level logger around the "router" that is the top level of my website; in one shot, you decorated the entire website. It's all just http.Handlers, there's no "real" distinction.
The community has a lot of nice middlewares that do things like basic auth, session management, cache headers, etc. If you use decorators and not middlewares, you'll have to write your own middleware code.
There's plenty of people with a lot of programming experience writing Go, and whose taste might just have diverged a bit from the "mainstream". :)
This is a cultural clash; there is an ample demand for a language with explicitly and harshly limited expressiveness, to prevent the C++ problems from ever happening. Go also is a safe enough language so I'd take it over C any moment where possible.
And boy it works...
I have to write a lot of Go myself. I detest it, and use it only because the industry dictates so.
Yes, I'd rather Sun go with an ML lookalike in 1997, but such was the intellectual fashion, and also computers were bitterly slow at the time, so a compiler had to be simple.
Not really. Generics, as a standalone feature, is not useful for much more than just container types. Go has generic container types, so woot, no need for plain generics.
Languages like Haskell which utilise generics for more useful things do so with other features. In Haskell's case, it's typeclasses, in other languages, it might be operator overloading. If you have a type T, you can't do shit with it unless you know something more about it. A type T that has a notion of + though can be checked at compile time and functions can "add" generically.
Well Go has interfaces. You can stick an Add() method on all the structs you might want to pass "generically" to a function, and add them.
So indeed, Go certainly is much simpler for having skipped "proper" generics, since they get to skip all the complicated stuff that makes them actually useful.
There are dozens of common types that give the lie to this (the standard Go claim), like sync.Map, sync.Value, lru, or pool.
Type classes are a feature that arises from having generics, not one that you need to take advantage of generics.