Sterling has too many projects Blogging about programming, microcontrollers & electronics, 3D printing, and whatever else...

Adventures in Go

| 2097 words | 10 minutes | golang
Changing Leaves

What a year, right? I had a whole load of blog posts planned for 2020, a talk to prep for YAPC, but the Lord decided that He had a better plan. I don’t know that anyone is going to read this, but I want to take some time to process what happened to the technical arm of it along the way.

The biggest change to my life technically is that I don’t think I have written more than a dozen edits to a Raku program in the last 15 months. I have written hundreds or thousands of lines of Perl, but more than anything else, the past year has been the year of Go for me. I had dabbled briefly in Go since 2018 as I had also dabbled a bit in Dart, JavaScript, and read a little bit about Rust, but I have now transferred much of my expertise into Go. Virtually all of my O(fun) work is now being written in Go (except for microcontroller firmware).

Where has the time gone?

I don’t know about you, but I have never been as busy in my life as I have been this year. Part of it is the ‘rona making me do some things myself that I used to have others do for me. Part of it is my kids keep getting older. Younger kids keep you from sleeping. Older ones keep you from boredom.

I also have a terrible gaming habit which tends to nibble away at what little is left of my free time. However, I still find time to write little bits of code here and there to solve problems in my spare time too. When the mood strikes me, I build a little app for this or that, solder some modules together to try and build a thing, or design a model to print on my Prusa.

In the past five years or so, these fits of coding have largely involved Raku (the language formerly known as Perl 6). I really like the Raku language because it is so expressive. When I want to write something, I don’t have to think, it just comes out stream of conscious. However, what I have come to hate about Raku is that it is a moving target that periodically breaks backwards compatibility, quickly evolving best practices, and has only a small (but supportive) community to help deal with these issues. That also means that module support is pretty poor and writing any particular application usually involves writing 3 or 4 new modules each time. When I had more spare time, this was fun. However, having to rewrite the accumulated stack of modules I’d written every year because of some language refactor or bug fix that happened to break all my modules became frustrating. I just don’t have time for that kind of messing around this year. When I want to build an application, I just want it to work. It was clearly time for a change.

I could have stuck to Perl, but I’ll be honest: Perl has done well at paying the bills, but I believe my current position is likely my last Perl position. That is, unless I spend the rest of my career porting Perl apps to something else, which is what most people hire Perl developers for from what I can tell. I have no interest in that work (though, I am already doing it at my current position at what is still a Perl-centric shop). Perl is now well past it’s prime and if it is to have a Renaissance, the Perl community will need to find a way to shift its demographics to include a much higher percentage of college students than it has today.

So why Go?

First, we’ve been using a lot of Go at work. In fact, if it weren’t for that, I probably would have learned Rust. I don’t really like Python and Ruby. Ruby had a bright but brief day in the sun and is now going the way of Perl itself. Python has hit its stride, but I suspect it’s going to be in Perl’s place as well in another decade or so. Lua is gross. Dart is overly complicated and reminds me of Java. Same goes for C#. Also I don’t like .NET, so anything in that ecosystem is not going to be interesting to me. I already said Raku is getting on my nerves (including a few other reasons I don’t really want to cover here). Perl is a good general choice, but a lot of the APIs I want to use don’t have Perl SDKs now, so I have to write my own SDKs and I’ve already said I don’t like that much. I’ve tried Scala and it tries to hard to be elegant, but when a class implements 100 roles to do simple tasks, it ceases to actually be elegant in my book. Haskell requires too much thinking and I’m again getting into a small dedicated community. Let’s Go with Go.

I want to get into this in more detail. Let’s start with how Go sucks and move on to what I like.

The Ugly

The aspect of Go that I truly despise is that it forces me to use cuddled elses. I really would like to write my code like this:

// A nice explanation of why
if a < 10 {
    return a + 1
}
// Another explanation of why else
else {
    return a + 2
}

However, the above code is a syntax error.1 The best I can do is something like:

if a < 10 {
    // A nice explanation of why
    return a + 1
} else {
    // A nice explanation of why else
    return a + 2
}

I miss my nicely commented uncuddled elses. I have long found that conditions are often the hardest to fathom out in code and being able to describe why they exist to be really helpful when I reread my own code months or years later. However, as long as I use Go, I will just have to learn to live without them.

The Bad

Go is also dead dreary to write. In some ways, it’s like writing instructions to programming the timers on a circa 1983 VCR. If you get that reference, you are officially old (or you have a weird fetish for watching YouTube videos about obsolete electronics). Basically, the process of writing Go works like this:

value, err := module.DoAThing(a, b)
if err != nil {
    fmt.Errorf(err, "Bad stuff: %w")
    return err
}

You repeat that about 100 times and you have a small Go program. It is completely lacking in the expressive elegance that Perl or Raku or even Python provides. Error handling is extremely manual and tedious.

However, you get used it and the availability of static analysis to help make sure you follow this pattern carefully does help to make up for it somewhat.

In theory, this pattern (combined with a static analyzer) forces the developer to decide how to handle each and every error. In practice, I find it’s little better than Java forcing developers to use a throws clause on methods. Lazy developers will simply insert panic() everywhere or assign the error to the anonymous _ to make the static analyzer shut up. Meanwhile, it is my experience that diligent developers handle errors well in just about any language regardless of how they work.

The Good

However, Go has a lot going for it. It has corporate backing from Google which means that things like bug fixing and security research and all that are covered more carefully than a small community open source project. Pure open source projects can develop that kind of support, but the corporate backing still (in my experience) lends a level of rigor and responsiveness to bug fixing that can be lacking in even a popular community supported project. (I should also note that corporate support does not guarantee that rigor and responsiveness, but it is present in this case.)

It also has a large interested community, so there are modules to do just about anything. Many third party APIs have their SDKs built in Go as well.

It might sound a little weird for a guy who’s made most of his money from Perl, but I like static typing. I always have. The type system of Go is very rigorous and pretty flexible. It has some odd wrinkles where it really could use some generics, but I haven’t needed type-case statements or delving into the reflection API very often.

As a point of contrast, in the past I approved of Raku’s gradual typing system, but in the end, most of Raku’s type system is still just a set of runtime type constraints. In most situations, it’s not much better than Modern Perl’s runtime constraints. Some of the type contraints in Raku can be optimized into compile time checks, but too many can’t be.

myInt := 4
myStr := myInt + " number" // static type check error

Go’s type system manages not to be intrusive and makes heavy use of a very simple form of type inference. Within a function, you often don’t have to worry about mentioning types explicitly if they can be inferred from the values or expressions being performed. You just use the := assignment and it handles the work of inferring your types for you.

Go doesn’t have any concept of inheritance, but has interfaces that provide a sort of duck-typing based on available methods. You can generally come up with an equivalent solution to inheritance. It does support a sort of mixin typing with structs, which works pretty well.

// (demo only, Go has it's own Rat type in math/big)
type Rat struct { n, d int }
type Int struct { v int }
func (r Rat) Int() int { return r.n / r.d }
func (i Int) Int() int { return i.v }
type Num interface { Int() int }
var n Num = Rat{10,5}
n = n.Int()

The best part of static typing is that if you write your code and tests well, you’ll get a compile time error for almost any refactoring changes until you’ve got them right. The compiler doesn’t guarantee your code works the way you want, but it does guarantee that your code is at least interfacing with itself in ai sane manner.

Speaking of testing, Go has strong built-in test tooling to encourage good testing. It also has strong tooling to perform static analysis to help find anti-patterns in code, make sure your code meets community standards that make it better for release and sharing, and so on.

Lastly, I want to mention that Go has excellent concurrency tooling. Just before I stopped doing much in Raku, I spent a lot of time learning how Raku handles concurrency and it is one of the areas where Raku syntax shines. It provides two primary concurrency constructs in the language (start and react) to handle running asynchronous tasks. Unfortunately, my experience is that since Raku is not very well optimized, these benefits are mitigated by the overhead of running Raku itself.

Go provides two very similar concurrency constructs (go and select), but the implementation is highly optimized so Go wastes very little time setting up or switching tasks around. However, where a Raku program. When I write a concurrent program in Go, the performance difference is obvious, which is not always true in Raku.

Concurrency is a major reason to select Go over languages like Perl and Python. These languages can mostly only run concurrent tasks through forking, which is expensive compared to a Go function or even a Raku start block. Anyway, I may be writing up an Advent calendar for 2021 to mirror my 2019 one, but in Go this time.

But really…

But really the decision comes down to this one question: Can I solve my general problems with it quickly? The answer so far is yes.

Cheers.


  1. For those that don’t use Go, the reason for the requirement that else be cuddled with the previous block has to do with the way statement breaks occur in Go. The short explanation is that Go speeds up the compiler by inserting implicit semi-colons at the end of most lines unless they end in certain characters like {. That means that a line ending in } is treated as if it were };, which means the following else is the start of a new statement, which is broken. ↩︎

The content of this site is licensed under Attribution 4.0 International (CC BY 4.0).