Abstract: 继续阅读 Scala with Cats, 开始学习 Monoids 和 Semigroups,其中文翻译分别为 幺半群(还有翻译成 独异点) 和 半群
数学背景
Monoids 和 Semigroups 实际上是严格的数学定义。
Group 群
G为非空集合,如果在G上定义的二元运算*,满足
- (1)封闭性(Closure):对于任意 $a,b∈G$,有 $a \times b∈G$
- (2)结合律(Associativity):对于任意 $a,b,c∈G$,有 $(a \times b) \times c=a \times (b \times c)$.
- (3)幺元(Identity):存在幺元 $e$,使得对于任意 $a∈G,e \times a=a \times e=a$
- (4)逆元:对于任意 $a∈G$,存在逆元 $a^{-1}$,使得 $a^{-1} \times a=a \times a^{-1}=e$
则称 $(G,\times)$ 是群,简称 $G$ 是群。
SemiGroup 半群
如果仅满足封闭性和结合律,则称G是一个半群(Semigroup);
Monoid 幺半群
如果仅满足封闭性、结合律并且有幺元,则称G是一个含幺半群(Monoid)。
举例
本书为了让读者理解这个数学概念举了很多例子,比如(整数,+) 就满足 封闭性,结合律和幺元的特性。
1 | // 封闭性 |
(String 类型,拼接操作 ++)也满足这些特性:
1 | // 封闭性 |
Scala 精简定义
(建议阅读本书时先 Semigroup 再看 Monoid)
首先我们为了从程序角度理解这两个概念,我们可以通过 Scala 来描述一下这两个概念:
1 | trait Semigroup[A] { |
这个只是接口,具体的类型和二元操作需要自行根据实际场景定义。比如整型和加法:
1 | object IntSemiGroup extends Semigroup[Int] { |
理论上我们需要验证我们的实现是不是符合半群的要求,比如减法并不符合结合律。
1 | def associativeLaw[B](x: B, y: B, z: B)(implicit m: Monoid[B]): Boolean = { |
Boolean 案例
我们可以定义4个 Monoid: And,Or,Either,XNOR(异或非)
1 | implicit val booleanAndMonoid: Monoid[Boolean] = |
为什么没有 异或 XOR?
Set 案例
Union: Monoid
Intersection:Semigroup
difference: None
symmetric difference: Monoid
1 | implicit def setUnionMonoid[A]: Monoid[Set[A]] = |
Cats 的 Monoids 实现
1 | import cats.Monoid |
Object
1 | import cats.Monoid |
Syntax
1 | import cats.instances.string._ // for Monoid |
使用案例
Monoid 是为某种类型提供的一种抽象的能力,这种能力是组合或相加。
SuperAdd
作为第一个案例,实现的功能很简单,就是实现一个 List 的累加,我们可以逐步提高这个累加功能的通用性:
累加 List[Int]
1 | import cats.Monoid |
这里利用了 foldLeft 算子来实现这个功能,如果不用 Monoid,可以直接使用该算子def add(items: List[Int]): Int = items.foldLeft(0)(_ + _)。
累加 List[A]
1 | import cats.Monoid |
如果 List 是一个泛型,这时候我们使用 Monoid 就有优势了,因为 Monoid 就是抽象了一种 combine 的能力,所以只要List 的泛型是 Monoid,就可以使用 Monoid 方法来处理合并。
累加自定义类
1 | case class Order(totalCost: Double, quantity: Double) |
上一节我们将 add 方法扩展到泛型,并且允许所有 Monoid 进行累加操作,对于基础类型,Cats 以及提供了Instance,对于自定义的类,需要自行实现 Monoid 的定义来处理 combine 操作。
Big Data
大数据合并结果:
- 计算访问人数,就是对 Int 的 Monoid 实例进行合并。
- 计算访问 unique 用户,就是对 Set[User] 的 Monoid 实例进行合并
- 计算 99% 相应时间,对 QTree 的 Monoid 实例进行合并
Distributed Systems
用于分布式系统一致性协议中,帮助进行不同节点数据的合并。
A particular class of data types support this reconciliation. These data types are called commutative replicated data types (CRDTs)
这里引出了一种数据类型:CRDTs,这就是一种具有 Monoid 实例的数据类型,具有合并的能力。我们在 Akka Distributed Data 的使用中会设计这种类型,帮助 Akka Cluster 实现数据的最终一致性。
总结
通过这个总结,我们用简短的语言再次描述一遍 Monoid 和 SemiGroups:
- 他们是
Type Classes的一种 - 他们提供了合并或相加能力的抽象
Monoid和SemiGroups都提供了合并或相加操作Monoid额外多一个空集或零元- 使用
Cats Monoid符合Type Classes的三部曲:Type Classes,Instance,Syntax or Object Cats Monoid三部曲cats.Monoidcats.instances.string._cats.syntax.semigroup._- 最佳用途:为依赖累加的方法提供抽象
所以当你遇到需要对某些数据类型进行累加或者合并的时候,就需要考虑一下是否引入 Monoid。