Monoid & MonoidK

In continuation to previous post.

There is no documentation for MonoidK (Monoid for higher Kinds) on the cats site, hence let me try explaining it. Though Monoid first.

Monoid

`Monoid[T]` technically is a Semigroup with an `empty`/`Identity` function. i.e. There are two main properties:

  • Associativity (From Semigroup)
  • Identity

Roughly:

trait Monoid[T]{
   /**
     combine is defined as: 
     `(a combine b) combine c =
                a combine (b combine c)`
   */
   def combine(x:T, y:T): T
   
   /**
   empty is defined as: `A o empty = empty o A = A`
    */
   def empty : T
}

An example:

    implicit def intMonoid = new Monoid[Int]{
      override def empty: Int = 0
      override def combine(x: Int, y: Int): Int = x + y
    }
    Semigroup[Int].combine(1,2) // returns 3


    implicit def listMonoid[T](implicit ev: Monoid[T]): Monoid[List[T]] = new Monoid[List[T]]{
      override def empty: List[T] = List()
      override def combine(x: List[T], y: List[T]): List[T] = x ::: y
    }
    Semigroup[List[Int]].combine(List(1,2), List(3,4)) //gives List(1,2,3,4)


    implicit def optionMonoid[T](implicit ev: Monoid[T]) : Monoid[Option[T]] = new Monoid[Option[T]]{
      override def empty: Option[T] = None

      override def combine(x: Option[T], y: Option[T]): Option[T] = x match {
        case Some(a) => y match {
          case Some(b) => Some(ev.combine(a, b))
          case None => None
        }
        case None => y
      }
    }

    Semigroup[Option[Int]].combine(Option(1), Some(2)) //gives Option(3)

    //This is quite awesome in the sense, that it even works for its inner type automatically. It is same as:

    Semigroup.apply[Option[Int]](optionMonoid[Int](intMonoid)).combine(Option[Int](1), Some[Int](2));

    //Many standard Monoids are available under below imports
    import cats._
    import cats.std.all._

    Monoid[String].combineAll(List())
    Monoid[Map[String,Int]].combineAll(List(Map("a" -> 1, "b" -> 2), Map("a" -> 3)))
    //Returns Map(b -> 2, a -> 4)

MonoidK

MonoidK is a Monoid for a kind (K stands for Kind). This is how it is roughly defined:

trait MonoidK[F[_]]{

   def combine[A](x:F[A], y:F[A]): F[A]
   def empty[A] : F[A]
}

We basically define the typeconstructor while declaring the `MonoidK` object and later define the type argument while calling `combine`. Its like you are working on the container and work based on its proper type provided later. One important thing to notice is that, you dont really stand a chance to work in nested. Example:

implicit def Ksemi[T]: SemigroupK[Option] = new SemigroupK[Option]{
    override def combine[A](x: Option[A], y: Option[A]): Option[A] = x orElse y
}

SemigroupK[Option].combine(Option(1), Option(2)) // returns Option(1)

implicit def listK : MonoidK[List] = new MonoidK[List] {
    override def empty[A]: List[A] = List()
    override def combine[A](x: List[A], y: List[A]): List[A] = x ::: y
}

MonoidK[List].combine(List(1,2,3),List(4))

When we define `SemigroupK[Option]`, we actually provide the type constructor. Doing so, leaves us no chance to provide implicit Monoid for its type parameter. So the behavior of `combine` is purely based on the container rather than the type argument.