Eric Weise

Finance and Technology Blog

Category Theory in Kotlin - Monoids

Introduction

I am starting a series of blog posts pertaining to a book that I am reading called Category Theory for Programmers by Bartosz Milewski. Bartosz has already done a series a posts on the topics in the book so instead of covering the same ground, my posts will be specifically about implementing the various category pattern in kotlin. I will try to make them as easy to understand as possible and provide useful examples. I won’t go into detail about actual category theory because the book does a much better job of that. I will just take a look at the pattern itself and explain why its useful and show an example in Kotlin

Monoids

Monoid is our first pattern. The name sounds like some disease you might catch or aliens from another planet. But like most names from category theory, it might sound a bit intimidating but the actual pattern is simple to understand. Let’s take a look at what a Monoid is

interface Monoid<T>{
    fun combine(t1:T, t2:T):T
    fun empty():T
}

There are two methods. The first is combine and it does pretty much what it says. It combines two values represented by <T>. The next function empty is less obvious. It takes no parameters and just returns a T. The return value however, need to represent a value that when combined with another T, will not change the other value. For example if I combine the value 3 with the value 0 then the result still 3.

There is one more aspect to Monoid and category theory patterns in general. In addition to specifying an interface, they also have laws. The law with Monoid is that the combine function must be associative, meaning that if I combine values A and B first and then combine that result with C, it should be the same as combining values B and C first and then combining A with that result. The order of precedence can change without changing the result. In math terms we say: (A + B) + C = A + (B + C). This law can’t be enforced by the compiler but users of your Monoid will expect it to be true.

Let’s see the power of monoids by applying the pattern to a custom data type Money. Here is our simple Money type

data class Money(val value: BigDecimal) {
    constructor(strValue: String) : this(BigDecimal(strValue))
}

It would be great if we could add our money so let’s create a Money Monoid.

object MoneyMonoid : Monoid<Money> {
    override fun combine(v1: Money, v2: Money): Money {
        return Money(v1.value.plus(v2.value))
    }

    override fun empty(): Money {
        return Money(BigDecimal.ZERO)
    }
}

Notice that the empty() method is implemented by returning the value 0.

Here is an example of our MoneyMonoid in action

assert(MoneyMonoid.combine(Money("1"), Money("2")) == Money("3"))

The MoneyMonoid succesfully added 1 + 2 to equal 3. OK that’s not so impressive since we could have just directly put an add function on the Money class. The power of the abstraction however, can be demonstrated by building additional functionality on top of Monoid. For instance, we can now create an extension method fold that combines any number of Monoids.

fun <T> Monoid<T>.fold(ts:List<T>):T {
    return ts.foldRight(this.empty()) { t: T, accumulator: T -> this.combine(t, accumulator) }
}

Notice that the fold function could be used with any Monoid, not just MoneyMonoid.

Now we can use this generic fold method to sum all our money

val listOfMoney = listOf( Money("1"),Money("2"),Money("3") )

assert(MoneyMonoid.fold(listOfMoney) == Money("6"))

We defined a very generic concept combining and were able to build upon it to create additional functionality. category theory patterns are all very generic and sometimes very abstract. They are built using interfaces but also have laws that must be enforced by each implementation.

18 Sep 2019