Meh

I have been working on a project that needs to run as small-footprint native code. I've been doing C since 1980 and have written a fair number of commercial packages in C, C++, Objective-C and blends among them.

But after a couple of decades doing Java and other managed languages, I no longer want to have to think about memory management.

Plus, the project is Docker related and most of the Docker ecosystem is on Go. So, I decided to do the project in Go.

Here are my "about a week in" thoughts.

The Comfortable

It took me a long time to grok Scala and even to get Clojure's idioms. I grew up on C-ish language and functional languages still require some effort for me to understand the motivation for the idioms.

Not so with Go. Go is a very, very comfortable language for me. I totally get it. I totally understand most of the design choices. I feel fairly proficient with Go. Yay!

The Good

There are some very good parts of Go.

Managed Memory

Managed memory is the first one. Coming from C/C++/ObjC-lang, not having to think about malloc/free, ref counting, etc. is just awesome. Managed memory was the first best thing for me when I started coding in Java 20ish years ago. Go got this one right.

Interfaces

Go got the balance right with Interfaces. It's so, so, so much nicer than the approach C++ takes. Over the years, I've come to really like structural typing over nominal typing. Interfaces are structural types and they work very well.

Also, having raw data (e.g., int64) that's promoted to data plus type information is a very slick system that puts boxing on the JVM to shame.

Goroutines

Perhaps the best part of Go is Goroutines, aka csp. Lightweight threading combined with a very well defined mechanism for sending data between threads is totally awesome. I mean, really, really awesome.

Writing multithreaded code in Go is very, very simple... as long as you don't mutate data in the messages.

Doing the default things right

Go is opinionated about where you place files and as long as you put files in the right place, there's no need for make files or a build system outside of Go.

Being able to reference dependencies by URL and doing go get to pull the dependent source code is really nice.

This is nice for small projects. I don't think it works for larger projects. Build systems exist for a reason.

The Bad

So, the above paints a very nice picture of Go. Too bad... because there's just so much that's really, really, really bad about the language.

Things are mutable

There's no real option for making things immutable and having immutable/persistent references is a cornerstone of writing multithreaded code.

Not enough expressions

if/else is a statement, not an expression. It leads to crappy code like:

val s string

if condition {
  s = "some value"
} else {
  s = "some other value"
}

There's not even a tenary operator. So, we've got 6ish lines of code for:

val s = if (condition) "some value" else "some other value"

Stupid syntax

There are so many really stupid, shoehorned syntax operations in Go. For example, looping over a collection requires the range thingy (is it a keyword, a function, an operation, what is range?):

for v := range data {
  ...
}

I mean, even Java has for (v in data) {...}. So, Go has this range thing that feels like a bolt-on.

Another bolt-on is how Go deals with type testing:

val x int

switch v := thing.(type) {
case string: x = len(v)
case int: x = v
}

In the above example, v gets magically cast based on its type. Compare this with Scala:

val x = thing match {
  case v: String => v.length()
  case v: Int => v
}

The stupid syntax hacks abound in Go. Each one makes sense in isolation, but as a collection, they are just a pain.

Not convinced... how about:

x := "thing"
val x2 = "thing" // same thing... different syntax

Too much opinionation

Go is very, very, very opinionated. It won't compile code if there's an unused variable, an unused import, and a whole lot of other perceived sins.

Yeah, I get it... if you're compiling for production, yes, barf on what would be warnings. But if I comment out a block of code while I'm in the development cycle, often I'll get compile errors... then I'll have to go comment out more code to get the compilation to work. This sucks. It breaks the quick compile and try cycles and without being able to use a REPL. So, Go development is about 30% slower than it needs to be.

1970s style error handling

No exceptions in Go. Only panic which shuts down the process gracelessly.

Anything that can fail returns a 2 element tuple... the return value and an error:

info, err := openAFile("/filename")

Just like every C programmer in the world who ignored error returns in the 1980s, this invites people to:

info, _ := openAFile("/filename")

Because, yeah, I'll get to that later.

I get that having exception handling semantics in closures, dealing with exceptions that unwind the stack from a Go routine, and all the other stuff like that is non-trivial to get right. But what's worse is to punt entirely. Go punted entirely.

So, we've got another programming language that encourages developers to not handle exceptional situations. Sigh.

Or put another way, if the Go designers had spent some time with Erlang they could have added a simple supervisor hierarchy to channels so when a channel dies, there's code that deals.

Some generics, but too little

Go supports some limited forms of generics for internal maps, arrays, channels and slices. Yay!

But the generics have horrid syntax:

myMap := map[string]interface{}{"foo": 33, "bar": "baz", "dog": true}

Yep... the second generic type is outside the square braces. Oh, yes, it's really horridly ugly.

Oh... are there are "multiple return values" aka, a generic product type... but you can't create one, you can't send one to a channel, you can only return one and the returned product type has to be unboxed with multiple assignment.

Also, you can't specify generics... and this leads to a lot of code copy/pasting. Just like in the C days.

No Collections Operations

It's 2015. Java and JavaScript have map and filter on collections. Go doesn't. This is stupid and wrong on its face.

How wrong? Well, so insanely stupidly wrong that I have no idea what the Go designers were thinking.

Didn't they spend, you know, 10 minutes with ANY OTHER LANGUAGE? map and filter are JUST WHAT YOU DO with collections.

Upper Case vs. Lower Case

If you give a function a lower case name, the function is private to something (maybe it's the local file, maybe it's the package... not really sure). Same for a field in a struct.

Upper case is public.

Yeah... this is a nice marker...

But many of the built in functions (e.g., len(...)) are lower case. So the Go built in stuff is lower case but public, but for everyone else, Upper Case for sharing.

The lack of consistency is frustrating.

So, Go is weak

My general take-away from Go is that it's a weak solution. It does a few things very well. It has a ton of syntactic and design problems. This is very unfortunate because there was a real chance for the Go team to do something well... and they didn't.