Monoid 是一个typeclass。因此,建议学习如何在 Scala 中对它们进行建模,它使用 implicits。
具体来说,Monoid[Map[K, V]] 的情况称为typeclass derivation,因为我们首先需要证明V 具有 Monoid,以便证明Map[K, V] 也有一个,对于所有Ks。
这是定义此类 typeclass 的规范方法,以及它的实例以及它们的操作/语法。
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)
}
}
}
然后你可以像这样使用它:
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)
最后,值得一提的是,虽然我相信第一次手工完成这些事情对于真正了解它们的工作原理非常棒。鼓励使用提供这些抽象的稳定且可用于生产的库,例如 Cats。
scalafiddle