Writing flexible Functions and Methods in Go using Generics
How to write functions with the flexible APIs offered by languages such as Ruby without sacrificing more type safety than strictly necessary. Then how to test them without excess code bloat.
Lots of fun will be had with Interfaces, Type Switches, Generics, and go test.
I love Go but have always found the way it handles collection types somewhat frustrating. In languages such as Ruby the collection classes are fleshed out with lots of functionality (batteries included) that in Go we have to roll for ourselves. It’s not that the same things can’t be done, it’s that there’s no clear template for how to go about doing so consistently. As a consequence of this I’ve been working on various packages for collection handling in Go for more than a decade now, some of them more practical than others.
Another of my interests is the use of Higher-Order Functions and Interfaces in API design. This topic doesn’t seem to get a lot of coverage despite Go having First-Class Function Closures. We’ll look at how to leverage Closures to write flexible functions and methods with strong type safety guarantees, as well as how to loosen those restrictions at runtime using Type Switches and Interfaces.
There was one very frustrating problem with these techniques prior to the introduction of Generics – the amount of code repetition necessary to support even the relatively small set of base types. This could be tackled using Reflection but that essentially abandons the speed of compiled code in favour of runtime interpretation as well as further degrading type safety. I don’t have a big problem with this on my personal projects but I feel bad doing it in production code.
Generics aren’t currently a perfect solution but they are very helpful so I’ll use them extensively throughout my examples and contrast them with alternative approaches.
Testing also benefits from both Closures and Generics so I’ll include examples of test code using both to write declarative tests and then to reduce the amount of code repetition involved.
If time allows I’ll include a Reflection example to highlight how much more legible Generic Functions are, and apply the testing techniques to benchmarking.
By the end of the session I’ll have covered these topics in sufficient depth that anyone with a basic knowledge of Go will be able to have fun exploring further.
- The workshop starts by looking at how collections of items can be represented in Go. This is intended to get everybody to the same basic level of understanding of collections and covers Arrays, Slices, Maps, Strings, Channels, and Functions. Depending on the audience this will take between 20 minutes and an hour.
- With this foundation we’ll be exploring the classic map/reduce problem where there’s a collection of items and we either want to modify all of these items in a similar manner, or we want to perform an operation that calculates a single value which somehow characterises that collection. The main goal of the workshop is to demonstrate how Generics work in this particular context as it’s one of their traditional strengths, and thereby to teach Generics in a very practical manner.
- However to get there we’re going to explore three concepts that are easy to grasp, two of which are unfortunately often presented as if they’re too complex for beginning or intermediate programmers when they’re not. Interfaces are one of the central features of Go’s type system and prior to Go 1.18 they were the main tool for writing generalised reusable code so we’ll cover these and look at how Generics change the way interfaces are used. This is a major change to the language which makes interfaces much more powerful with only a little extra complexity.
- Where Interfaces hit their limits in earlier versions of Go we would then turn to the Reflect package which is a way to ask the Go runtime what it knows about an arbitrary value so that we can then reason about it programatically. Reflection has a terrible reputation for being complex and confusing because the package documentation is extensive but lacks examples. However in practice Reflection follows very similar patterns to those used in type switches and we’ll examine these in a manner suitable even for newcomers to Go.
- More interesting is the potential for using Function Closures (also known as First Class Functions in computer science) to decouple operations such as Map or Reduce from the task to be performed. Function Closures are another core feature of Go’s type system and a very powerful one but they’re not used often in the standard package library which makes them seem like an esoteric feature when in practice they’re very simple to work with and understand. This is one of the key computer science concepts to learn if you’re a beginner and we’ll do our best to do it justice.
- The Language Specification for Generics is much longer and more complicated than for any other feature of Go so as we introduce them we’re going to completely ignore the Spec and instead focus on code examples which show each of the key concepts introduced. If you’re a beginner to Go this will probably be the most difficult part of the workshop however by keeping everything grounded in concrete examples everything will be much easier to understand.
WHAT AM I GOING TO LEARN?
The key concept this workshop is aiming to get across is the power of generality in decoupling code and how this both simplifies code and increases its reusability. We’ll also cover a number of computer science concepts which every programmer should be familiar with to deepen their knowledge of their craft and of system architecture.
The accompanying slides and git repository will provide a detailed resource for offline study.
Any laptop which can run Go 1.18 with your preferred development environment whether that’s an IDE or a basic text editor.
This can be as simple as a ChromeBook if you have the Linux developer mode enabled, any 64-bit Apple laptop, a Windows or Linux laptop, or even an Android device with Termux installed.
Make sure to test your Go installation beforehand and ensure that it compiles a basic Hello World program.
No third-party packages will be used so there are no additional dependencies.
Innovative Identity Solutions
Hacker Ellie is the sometime writer of A Go Developer’s Notebook. Her career has spanned three decades working on projects ranging from mission critical avionics and broadcast transmission networks to banking security and digital trust arbitration. Her languages of choice include Golang and Ruby whilst necessity often finds her working with C and Bash.
Ellie is co-founder of Innovative Identity Solutions, a London-based startup focused on driving innovation in digital identity, personal data privacy and secure communications with a particular emphasis on novel blockchain architectures. Along with her business partner she’s co-inventor of several patents in digital identity and biometric liveness.
As a responsible parent Ellie enjoys polyhedral dice, home brewing and gothic music.
Expert in: Ruby, Go, C, Bash.