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.Monoid
cats.instances.string._
cats.syntax.semigroup._
- 最佳用途:为依赖累加的方法提供抽象
所以当你遇到需要对某些数据类型进行累加或者合并的时候,就需要考虑一下是否引入 Monoid
。