HOME - BLOG POSTS - TYPELEVEL CATS

# What is a Monoid?

- 3 minutes read - 569 wordsWe often need to combine data types that may be “empty” e.g. empty `Lists`

or `Options`

. How do we combine a `List[Int]`

to produce an `Int`

if the `List`

is empty? What is the result? How about `List[String] => String`

. Logically we would
expect zero for the `Int`

example and "” for the `String`

but how do we represent this?

The simple *Monoid* extends *Semigroup* and adds a default or fallback value for the given type.
It allows us to combine empty data types so long as we have a Monoid instance

You can find complete examples of the concepts discussed on my blog in my Github repo. In particular, check out the Monoid example

### Introduction

If you haven’t already read my post about Semigroups I suggest you do so now.
Semigroup provides `combine(x: A, y: A): A`

so we can easily combine integers, strings etc. In my semigroup example I
showed how we can use Semigroups along with recursion or `fold`

to combine a collection of elements together.

Let’s take another look at the example using a fold:

```
def combineStrings(collection: Seq[String]): String = {
collection.foldLeft("")(Semigroup[String].combine)
}
```

`foldLeft`

needs a default or fallback value, in this case I’m passing an empty String (""). My recursive example also
needs the same fallback value:

```
def combineStrings(collection: Seq[String], acc: String = ""): String = ???
```

Again I’m using an empty String. Why do we need this?

### The identity/empty element

The answer is in the types: `combineStrings`

takes a `Seq[String]`

. The Seq can be empty but we still need to return a String.
In our example if we pass an empty Seq we get the empty String back. The empty String we’re passing here is known as the identity
or empty value, we can think of it as a fallback or default value.

A Monoid is simply a Semigroup with an Empty element

Lets look at the signature of the Monoid type class

```
trait Monoid[A] {
def combine(x: A, y: A): A
def empty: A
}
```

This simple addition to Semigroup is actually really useful as it lets us write generic code e.g.

```
def combineAnything[A](collection: Seq[A])(implicit ev: Monoid[A]): A = {
val monoid = Monoid[A]
collection.foldLeft(monoid.empty)(monoid.combine)
}
```

So long as we have a Monoid instance in scope for `A`

we can handle it. We can even write a very basic map-reduce framework!

```
def mapReduce[A, B](collection: Seq[A])(op: A => B)(implicit ev: Monoid[B]): B = {
val monoid = Monoid[B]
collection.map(op).foldLeft(monoid.empty)(monoid.combine)
}
import cats.instances.int._ // Monoid implementation for ints
val result = mapReduce(Seq(1, 2, 3)) { _ * 2 }
```

### Identity/empty in detail

As you might expect there’s a little more to the identity/empty element than meets the eye! In simple terms the identity element should be a no-op in context of combine i.e.

```
combine(x, empty) == combine(empty, x) == x
```

Looking at some examples helps to clarify this:

```
// Integer addition using 0 as an identity
1 + 0 == 0 + 1 == 1
// Integer multiplication using 1 as an identity
2 * 1 == 1 * 2 == 2
// Integer multiplication using 0 as an identity (not ok!)
2 * 0 == 0 * 2 // Ok
2 * 0 == 2 // Ouch! 0 != 2
```

So it’s clear the empty/identity element depends on the context not just on the type. That’s why Monoid (and Semigroup) implementations are specific not only to the type but also the combine operation