Monoid
API Documentation: Monoid
Monoid extends the power of Semigroup by providing an additional empty value.
trait Semigroup[A] {
def combine(x: A, y: A): A
}
trait Monoid[A] extends Semigroup[A] {
def empty: A
}
This empty value should be an identity for the combine operation, which means the following equalities hold
for any choice of x.
combine(x, empty) = combine(empty, x) = x
Many types that form a Semigroup also form a Monoid, such as Ints (with 0) and Strings (with "").
import cats.Monoid
implicit val intAdditionMonoid: Monoid[Int] = new Monoid[Int] {
def empty: Int = 0
def combine(x: Int, y: Int): Int = x + y
}
val x = 1
Monoid[Int].combine(x, Monoid[Int].empty)
// res1: Int = 1
Monoid[Int].combine(Monoid[Int].empty, x)
// res2: Int = 1
Example usage: Collapsing a list
In the Semigroup section we had trouble writing a generic combineAll function because we had nothing
to give if the list was empty. With Monoid we can return empty, giving us
def combineAll[A: Monoid](as: List[A]): A =
as.foldLeft(Monoid[A].empty)(Monoid[A].combine)
which can be used for any type that has a Monoid instance.
import cats.syntax.all._
combineAll(List(1, 2, 3))
// res3: Int = 6
combineAll(List("hello", " ", "world"))
// res4: String = "hello world"
combineAll(List(Map('a' -> 1), Map('a' -> 2, 'b' -> 3), Map('b' -> 4, 'c' -> 5)))
// res5: Map[Char, Int] = Map('b' -> 7, 'c' -> 5, 'a' -> 3)
combineAll(List(Set(1, 2), Set(2, 3, 4, 5)))
// res6: Set[Int] = HashSet(5, 1, 2, 3, 4)
This function is provided in Cats as Monoid.combineAll.
The Option monoid
There are some types that can form a Semigroup but not a Monoid. For example, the
following NonEmptyList type forms a semigroup through ++, but has no corresponding
identity element to form a monoid.
import cats.Semigroup
final case class NonEmptyList[A](head: A, tail: List[A]) {
def ++(other: NonEmptyList[A]): NonEmptyList[A] = NonEmptyList(head, tail ++ other.toList)
def toList: List[A] = head :: tail
}
object NonEmptyList {
implicit def nonEmptyListSemigroup[A]: Semigroup[NonEmptyList[A]] = _ ++ _
}
How then can we collapse a List[NonEmptyList[A]] ? For such types that only have a Semigroup we can
lift into Option to get a Monoid.
import cats.syntax.all._
implicit def optionMonoid[A: Semigroup]: Monoid[Option[A]] = new Monoid[Option[A]] {
def empty: Option[A] = None
def combine(x: Option[A], y: Option[A]): Option[A] =
x match {
case None => y
case Some(xv) =>
y match {
case None => x
case Some(yv) => Some(xv |+| yv)
}
}
}
This is the Monoid for Option: for any Semigroup[A], there is a Monoid[Option[A]].
Thus:
import cats.Monoid
import cats.data.NonEmptyList
import cats.syntax.all._
val list = List(NonEmptyList(1, List(2, 3)), NonEmptyList(4, List(5, 6)))
val lifted = list.map(nel => Option(nel))
Monoid.combineAll(lifted)
// res8: Option[NonEmptyList[Int]] = Some(
// value = NonEmptyList(head = 1, tail = List(2, 3, 4, 5, 6))
// )
This lifting and combining of Semigroups into Option is provided by Cats as Semigroup.combineAllOption.
N.B.
Cats defines the Monoid type class in cats-kernel. The
cats package object
defines type aliases to the Monoid from cats-kernel, so that you can simply import cats.Monoid.