Option Monad in Go

Option Monad in Go

·

4 min read

In the previous article "Functional Programming in Go?" we tested the waters to show that some functional programming concepts and Go language could get along.

In this article we will explore a functional programming pattern called Monad and one of its canonical implementations - Option type.

As many functional programming concepts, Monads originate from Category theory. And while it's nice to know that a useful development pattern finds its roots in mathematics, we are going to look at this concept from the practical angle only.

Practically, a Monad type is a container type M that wraps some value A and has 2 operations defined: Unit(that wraps a value of type A into a Monad M) andFlatMap(that transforms a wrapped value A to a value B) :

type M[A any] struct {
    value A
}
func Unit[A any](a A) M[A] 
func FlatMap[A, B any](m M[A], f func(a A) M[B]) M[B]

It's quite likely that for a person with no prior experience with Monads, this definition would not click right away. To answer a question "but why it is useful?" we will implement a simple but handy Option Monad.

Option type represents some value that might exist or might not. In Go it could be defined using a simple structure like this:

type Option[A any] struct {
    value A
    defined bool
}

Also we will define 2 functions for creating an Option. These functions effectively represent Unit operation from the Monad definition above (Some wraps a value into Option, while None represents a lack of value):

func Some[A any](value A) Option[A] {
    return Option[A]{value, true}
}

func None[A any]() Option[A] {
    return Option[A]{defined: false}
}

One benefit of using Option type in the code is that it states a developer's intentions clearly. If we see a function returning Option[string] we know that a value might not exist and we are forced to process that case explicitly. We know that if a function returns string then it has to have some meaningful value which we can use in our logic right away, without further checks. But having Option[string] indicates that it might not be the case.

However, even though using our Option type would serve this purpose, it will not be enough to convince how it's better than using another more idiomatic way to handle optional values in Go - returning multiple values like value, ok = getSomeOptionalValue(). But when we complete our Option type implementation by adding a FlatMap operation, it should make some difference. We will define it like that:

func FlatMap[A, B any](option Option[A], f func(A) Option[B]) Option[B] {
    if option.defined {
        return f(option.value)
    } else {
        return None[B]()
    }
}

Now imaging we have a chain of computations where each next computation depends on the previous one, but each computation might not return a successful result (a result is optional):

func calculate1() Option[int] {
    return Some(100)
}

func calculate2(a int) Option[int] {
    return Some(a + 200)
}

func calculate3(a int) Option[string] {
    return Some(fmt.Sprint(a))
}

func calculate4(a string) Option[string] {
    return Some("abc " + fmt.Sprint(a))
}

Using our Option Monad we could express the overall computation like that:

FlatMap(FlatMap(FlatMap(calculate1(), calculate2), calculate3), calculate4)
// Some(abc 300)

And if it happens that any of these computations yields None, then the overall result of the computation would become None as well:

FlatMap(FlatMap(FlatMap(None[int](), calculate2), calculate3), calculate4)
// None

Essentially, Option Monad could help to chain computations with optional results, thus helping to reduce boilerplate code which is otherwise needed for dealing with non-present values.

Conclusion

We've touched slightly a concept of Monads from practical perspective, and implemented a simple Option Monad. Of course, apart from the operations which we defined for Option type, there are many more common functional programming combinators helping to make Options even more useful in practice. If you are interested to find out more or dive deeper into the topic of functional programming in Go, I would be more than happy to see you joining me on GitHub (github.com/ialekseev/go4fun). We can play together :) Thanks for reading!