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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With