intro

Apparently, it’s common in blog posts about a language to start with the reasons why I find it fantastic.

Yes, I love Golang for its simplicity (which yet leads to complexity 😁). Its runtime is really fast, you can quickly deliver stable builds without any external dependencies…

For a DevOps like me, to write CLIs or small Web Application such as proxies, it’s perfect.

Now that this ceremony is done, let’s focus on concrete examples and tips with GoLang. Lot of them will involves generics, so you can already install GoLang 1.18 😎

If you don’t have it installed, you can follow the latest instructions to have the beta2 version (latest available when i was writing the article)

GoLang and Generics benefits : ADTs part1

A bit of context : ADT here stands for Algebraic Data Type. You might wonder what the hell is this ?

Here’s a small definition : An algebraic data type is a structured type that’s formed by composing other types. Or, even shorter, it’s a type made of other types. (you can find more information about ADT on the link).

So, for instance :

type ADT struct {
    A int
    B string
}

is an ADT, since it’s a type made of 2 types : an int and a string. This is namely a Product Type because we combine multiple types together. Cool. But there’s another type of ADT : Sum Type

Sum Types by example : Option

Sum types are disjoint union of 2 types.

For instance, a type that can be Either an int OR a string.

In this first part we’ll focus on a specific Sum Type, yet a very common one : Option. An Option of int is either an int OR … nothing. How would you do that in Go ?

… I’ll let you think about it …

faded_fp_dev

Pointers are Option

Yes, pointers are a way to represent Optional value. A pointer of int is either an int or nil.

Where to use option

Option are very common when you have to deal with REST API and especially JSON encoded responses. Let’s simulate that with this little piece of code, where we handle JSON with a mandatory question and an optional answer.

We want to display the question and the upper-cased answer if any:

{{< gist rguilmont 8993ee2da30f36239bf278544005006a “example1.go” >}}

It compiles perfectly well !

But run it and you’ll get :

The asked question was : what is your name?
The upper case answer is MY NAME IS EVENS

The asked question was : how old are you?
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4ba301]

goroutine 1 [running]:
main.processResponse(0xc00001e160, 0x10, 0x0)
    /home/runner/golang-generics-1-adt/example2/main.go:30 +0xc1
main.main()
    /home/runner/golang-generics-1-adt/example2/main.go:36 +0x1a7
exit status 2

Boom. It crashed.

We assumed the pointer had a value, but it didn’t. Yet it compiles, because at compile time there’s no way to check whether the pointer actually references a value or not. We should add the following check :

{{< gist rguilmont 8993ee2da30f36239bf278544005006a “example1_2.go” >}}

For the code to be safe. But the compiler won’t force you to do so.

fanatic_gopher_1

Well, once you start to have big nested structures with a lot of optional fields, your code can quickly start to look like a giant pile of if ... != nil.

Not very pleasant.

Writing an Option type

So far, we’ve seen that :

  • Option are a disjoint union between a Type and Nothing
  • Pointers are a way to represent Option, but they are not safe at runtime.

Right. So how could we have a runtime safe Option in golang ? With an interface, we can write something like this :

{{< gist rguilmont 8993ee2da30f36239bf278544005006a “example2_1.go” >}}

This way we never deal with the string, but with an OptionalString. Nice, but we can’t do anything with that. We need some functions to work with this new type :

{{< gist rguilmont 8993ee2da30f36239bf278544005006a “example2_2.go” >}}

It would also be neat to be able to unmarshal a json field directly into an option right ? Unfortunately we can’t : OptionalString is an interface. So let’s use another type to do the boilerplate for us.

{{< gist rguilmont 8993ee2da30f36239bf278544005006a “example2_3.go” >}}

😫 Ok this time we’re done !! We have :

  • An Optional string type
  • A way to decode JSON field into an OptionalStringDecoder

Let’s refactor our initial code with the OptionalString :

{{< gist rguilmont 8993ee2da30f36239bf278544005006a “example3.go” >}}

Run it, and you should get :

> go run main.go 
The asked question was : what is your name?
The upper case answer is MY NAME IS EVENS

The asked question was : how old are you?

The output is pretty basic, but it does exactly what we wanted : display the uppercase answer if any, nothing else. And more important, this is all safe at runtime !! 🎉

OptionalInt, OptionalBool, OptionalDouble…

Next step is, without a doubt, to move OptionalString and OptionalStringDecoder to its own module, or at least package.

Then, let’s write OptionalInt, OptionalBool, OptionalDouble, OptionalMyStruct1, OptionalYourStruct, OptionalSliceOfInt, OptionalSliceOfDouble, …

… you see what I mean, don’t you ? 😇

We need a magic trick, introduced with Golang 1.18 : Generics (or Type Parameters).

This article was just an introduction to show you that, thanks to OptionalString type we wrote, we can represent the absence of a value for a string, in a runtime safe way and without having to deal with pointers.

In the next part, we’ll see how generics are a key to have type-safe Option without having to repeat ourselves. We’ll also introduce more functions on Option to unleash the full power of it 💪. We’ll use the library RinauGo as a main material.

Yes, Generics are awesome, and we can do very advanced stuff thanks to it !

Conclusion