4
trait Monoid[A] {
  def op(a1: A, a2: A): A

  def zero: A
}

def mapMergeMonoid[K, V](V: Monoid[V]): Monoid[Map[K, V]] = new Monoid[Map[K, V]] {
    override def op(a1: Map[K, V], a2: Map[K, V]): Map[K, V] =
      (a1.keySet ++ a2.keySet).foldLeft(zero) {
        (acc, k) => acc.updated(k, V.op(a1.getOrElse(k, V.zero), a2.getOrElse(k, V.zero)))
      }

    override def zero: Map[K, V] = Map[K, V]()
  }

As I understood, i can concat 2 Maps with this Monoid. But I cant understand, how to use it. What i have to put into (V: Monoid[V]) argument to use op method after and put there 2 Maps.

andrew17
  • 851
  • 2
  • 10
  • 25

2 Answers2

5

Monoid is a typeclass. Thus, it is recommended to learn how we model them in Scala, which is using implicits.
Specifically the case of the Monoid[Map[K, V]] is the one known as typeclass derivation, because we will first require a proof that V has a Monoid, in order to prove that Map[K, V] also has one, for all Ks.

Here is the canonical way to define such typeclass, together with its instances as well as their ops / syntax.

trait Monoid[A] {
  def op(a1: A, a2: A): A

  def zero: A
}

object Monoid {
  implicit final val IntMonoid: Monoid[Int] = 
    new Monoid[Int] {
      override final def op(i1: Int, i2: Int): Int =
        i1 + i2

      override final val zero: Int = 0
    }

  implicit def mapMonoid[K, V](implicit vm: Monoid[V]): Monoid[Map[K, V]] =
    new Monoid[Map[K, V]] {
      override final def op(m1: Map[K, V], m2: Map[K, V]): Map[K, V] =
        (m1.keySet | m2.keySet).foldLeft(this.zero) {
          case (acc, key) =>
            acc + (key -> vm.op(
              m1.getOrElse(key, default = vm.zero),
              m2.getOrElse(key, default = vm.zero)
            ))
        }

      override final val zero: Map[K, V] = Map.empty
    }
}

object syntax {
  object monoid {
    implicit class MonoidOps[A] (private val a1: A) {
      def |+| (a2: A)(implicit M: Monoid[A]): A =
        M.op(a1, a2)
    }
  }
}

Which you can then use like this:

import syntax.monoid._ // Provides the |+| operator.

Map('a' -> 1, 'b' -> 2) |+| Map('b' -> 3, 'c' -> 5)
// res: scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 5, c -> 5)

Finally, it is worth mentioning that while I believe that doing these things by hand the first time is great to really understand how they work under the hood. It is encouraged to use stable and production-ready libraries, that provide these abstractions, for example Cats.

scalafiddle

Scalway
  • 1,633
  • 10
  • 18
2

Say we want to combine two maps of type Map[Int, String]

val a1: Map[Int, String] = Map(1 -> "Picard")
val a2: Map[Int, String] = Map(1 -> "Worf", 2 -> "Data")

Then V becomes String which means we need to provide Monoid[String] instance in order to specify how Vs will be combined

val stringMonoid: Monoid[String] = new Monoid[String] {
  override def op(a1: String, a2: String) = a1 + a2
  override def zero = ""
}

Putting it together we have

mapMergeMonoid(stringMonoid).op(a1, a2)

which outputs

res0: Map[Int,String] = Map(1 -> PicardWorf, 2 -> Data)

Conceptually, monoid provides a way of combining values, so when defining how to combine maps of type Map[K, V] it makes sense we would need to also specify how the values V of the map combine themselves. Hence Monoid[V] is a necessary constituent element in the definition of Monoid[Map[K, V]]:

Map[A, B] is a Monoid if B is a Monoid

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • Understand now, thanks. Is it really more comfortable to use Scala Worksheet instead of common files? – andrew17 Dec 22 '19 at 16:08
  • 1
    @andrew17 Personally I find _Scala Worksheets_ to be excellent learning tool. Remember to set [`Plain`](https://stackoverflow.com/a/57913651/5205022) evaluation model when experimenting with typeclasses in Scala Worksheets. – Mario Galic Dec 22 '19 at 16:12