Golang

When Code Guarantees Are Checked

As with many programmers, as my career has progressed I’ve come to appreciate the utility of “compile time checking”. I don’t ever want to go back to a language where it is hard to verify constraints are checked at compile time.

Followers of this blog may know that I do a lot of programming in Go, and find it hard to square that attitude with the use of Go, which has relatively weak compile-time guarantees.

The Uselessness of "Fast" and "Slow" in Programming

Every field of human activity has its unique characteristics, and programming is no exception.

One of the unique aspects of software is how it spans such a large number of orders of magnitude. A software engineer may be slicing and dicing nanoseconds, or they may be trying to accelerate a computation that will run across thousands of cores for a month… and they may even be doing both at the same time!

On Boxes and Lines and Layered Design

In my previous post, Layered Design in Go, I discussed a procedure that can be used in any Go module to order the packages in an order of what depends on what, starting with the modules on the bottom that have no internal dependencies, then the modules that only depend on those, and then so on.

Conceptually it’s a useful way to think about Go modules, and any other system that similarly forbids circular imports.

Layered Design in Go

This post will describe how I design my programs in Go. I needed this for work, and while I searched for a link, nothing quite fits my coding practices out there. The word “Layered” can pull up some fairly close descriptions, but I want to lay out what I do.

Deriving Some Requirements

Go has a rule that I believe is underappreciated in its utility and whose implications are often not fully grasped, which is: Packages may not circularly reference each other. It is strictly forbidden. A compile error.

Go FAQ: The Pipe Operator In Generics Is Not A Sum Type

An increasingly-frequently asked question in the Go subreddit is some variant of: “I have this code but it isn’t doing what I expect it to do.”

type MyStruct struct {
    // definition
}

type OtherStruct struct {
    // definition
}

type MySumType interface {
    MyStruct | OtherStruct
}

func main() {
    myStruct := MyStruct{...}
    printVal(myStruct)
}

func printVal[T MySumType](in T) {
    switch val := in.(type) {
    case MyStruct:
        fmt.Println("It's a MyStruct")
    case OtherStruct:
        fmt.Println("It's an OtherStruct")
    }
}

There’s a variety of manifestations of the question at this point, such as, “the switch won’t compile” or “I can’t take a MySumType as a type parameter on a function”, or a couple of other varients, but the specific variant doesn’t matter because there is no syntax tweak to fix this, because this is not what the | operator is.

Validity of Values In Programming Languages

A point touched upon in my Interfaces and Nil in Go, or, Don’t Lie to Computers that deserves full expansion is, what exactly does it mean for a value to be valid?

It is something that I think many would consider obvious at first, but if you dig into it… and programming generally does make us dig into things like this… it becomes less obvious.

But if you are one who thinks it’s obvious, riddle me this. I have a string containing the six characters “here's”.

Is it valid?

Abuse of Sum Types In OO Languages

Sum types are useful, but they are also an attractive nuisance in object oriented languages. There's a certain type of programmer who gets a taste of functional programming and has a good time, but misinterprets that good time to mean that sum types are always better, because they are FP, and FP is Better.

(If you'd like my full opinion on functional programming in object-oriented languages, see my Functional Programming Lessons in Imperative Code BlogBook. See that BlogBook also if you want to rip into me about my opinions of FP expressed in this post.)

Interfaces and Nil in Go, or, Don't Lie to Computers

It is commonly held up as a wart in Go that interfaces have "two different nils"; one is when the interface value is nil:

var something interface{}
fmt.Println(something == nil) // prints true

and one is when the interface contains a nil:

var i *int // initializes to nil
var something interface{} = i
fmt.Println(something == nil) // prints false

This is not a wart in Go. It is a result of a programmer misconception combining with a software engineering bug which results in an attribution error.

Why Go Getting Generics Will Not Change Idiomatic Go

After years of overly-acrimonious online discussion, Go is continuing down the chute towards getting generics. I'm already seeing the inevitable "functional" libraries (plural) coming out. I'm going to explain why none of these libraries are going to materially impact what the community considers good style.

They may see some use. I may even use some of them myself on small scales. Some iconoclasts will program with them extensively, and their code will work, in the sense that it will do what it was designed to do regardless of how the code reads. But they are not going to change what generally-good style is considered to be.

Edit: May 2021 - Update for the latest generics syntax.

Avoiding Reflection (And Such) In Go

So, as previous posts show, I like Go well enough. But as a computer-language polyglot, "Go programmer" is not part of my identity, and I try for a balanced view. It is very human to go overboard in both praise and criticism, both easy to find online.

I open with this because this post will take a, ahh, let's say nuanced position on Go, in that it is going to agree a bit with both sides. Go's type system is weak and there are cases where you can only accomplish something via interface{}, reflection, copy/paste, or code generation... but in a rush to talk about what Go doesn't have, what it does have is too often neglected. When using a tool to solve problems what matters is whether there exists a good solution with that tool, not whether a direct port of a good solution from some other tool works. There certainly are real problems that lack good solutions in Go, but that set of problems is smaller than is sometimes supposed.

Some languages provide many power tools, and it is easy to use a bit of one and a bit of another without ever using any individual tool very deeply. Go provides you only a few tools, so you should use each fully.

If your Go code is a horrible copy/paste disaster, you must figure out whether it is because you are not using tools fully, or if you are missing tools. If you are missing tools, use another tool set. I do not consider that any sort of "concession", because I'm not too interested in whether you use Go; I'm interested in showing people how to fully use tools. All of these techniques will work in other languages as well, with varying degrees of convenience.