Monoid是种最简单的typeclass类型。我们先看看scalaz的Monoid typeclass定义:scalaz/Monoid.scala
1 trait Monoid[F] extends Semigroup[F] { self =>
2 ////
3 /** The identity element for `append`. */
4 def zero: F 5 ...
Monoid trait又继承了Semigroup:scalaz/Semigroup.scala
1 trait Semigroup[F] { self =>
2 ////
3 /** 4 * The binary operation to combine `f1` and `f2`. 5 * 6 * Implementations should not eva luate the by-name parameter `f2` if result 7 * can be determined by `f1`. 8 */
9 def append(f1: F, f2: => F): F 10 ...
所以获取一个类型的Monoid实例需要实现zero和append这两个抽象函数。实际上Monoid typeclass也就是支持了append(|+|)这么一个简单的操作。scalaz为一些标准类型定义了Monoid实例:
1 0 |+| 30 //> res0: Int = 50
2 20.some |+| 30.some //> res1: Option[Int] = Some(50)
3 List(1,2,3) |+| List(4,5,6) //> res2: List[Int] = List(1, 2, 3, 4, 5, 6)
4 Tags.Multiplication(3) |+| Monoid[Int @@ Tags.Multiplication].zero 5 //> res3: scalaz.@@[Int,scalaz.Tags.Multiplication] = 3
6 Tags.Conjunction(true) |+| Tags.Conjunction(false)//> res4: scalaz.@@[Boolean,scalaz.Tags.Conjunction] = false
7 Tags.Disjunction(true) |+| Tags.Disjunction(false)//> res5: scalaz.@@[Boolean,scalaz.Tags.Disjunction] = true
8 Monoid[Boolean @@ Tags.Conjunction].zero //> res6: scalaz.@@[Boolean,scalaz.Tags.Conjunction] = true
9 Monoid[Boolean @@ Tags.Disjunction].zero //> res7: scalaz.@@[Boolean,scalaz.Tags.Disjunction] = false
就这么来看好像没什么值得提的。不过Ordering的Monoid倒是值得研究一下。我们先看看Ordering trait:scalaz/Ordering.scala
1 implicit val orderingInstance: Enum[Ordering] with Show[Ordering] with Monoid[Ordering] = new Enum[Ordering] with Show[Ordering] with Monoid[Ordering] { 2 def order(a1: Ordering, a2: Ordering): Ordering = (a1, a2) match { 3 case (LT, LT) => EQ 4 case (LT, EQ | GT) => LT 5 case (EQ, LT) => GT 6 case (EQ, EQ) => EQ 7 case (EQ, GT) => LT 8 case (GT, LT | EQ) => GT 9 case (GT, GT) => EQ 10 } 11
12 override def shows(f: Ordering) = f.name 13
14 def append(f1: Ordering, f2: => Ordering): Ordering = f1 match { 15 case Ordering.EQ => f2 16 case o => o 17 } 18 ...
这里定义了Ordering的Monoid实例。它的append函数意思是:两个Ordering类型值f1,f2的append操作结果:假如f1是EQ就是f2,否则是f1:
1 (Ordering.EQ: Ordering) |+| (Ordering.GT: Ordering) 2 //> res8: scalaz.Ordering = GT
3 (Ordering.EQ: Ordering) |+| (Ordering.LT: Ordering) 4 //> res9: scalaz.Ordering = LT
5 (Ordering.GT: Ordering) |+| (Ordering.EQ: Ordering) 6 //> res10: scalaz.Ordering = GT
7 (Ordering.LT: Ordering) |+| (Ordering.EQ: Ordering) 8 //> res11: scalaz.Ordering = LT
9 (Ordering.LT: Ordering) |+| (Ordering.GT: Ordering) 10 //> res12: scalaz.Ordering = LT
11 (Ordering.GT: Ordering) |+| (Ordering.LT: Ordering) 12 //> res13: scalaz.Ordering = GT
如果我用以上的特性来比较两个String的长度:如果长度相等则再比较两个String的字符顺序。这个要求刚好符合了Ordering Monoid实例的append操作:
1 3 ?|? 4 //> res14: scalaz.Ordering = LT
2 "abc" ?|? "bac" //> res15: scalaz.Ordering = LT
3 def strlenCompare(lhs: String, rhs: String): Ordering =
4 (lhs.length ?|? rhs.length) |+| (lhs ?|? rhs) //> strlenCompare: (lhs: String, rhs: String)scalaz.Ordering
5
6 strlenCompare("abc","aabc") //> res16: scalaz.Ordering = LT
7 strlenCompare("abd","abc") //> res17: scalaz.Ordering = GT
这个示范倒是挺新鲜的。
好了,单看Monoid操作会觉着没什么特别,好像不值得研究。实际上Monoid的主要用途是在配合可折叠数据结构(Foldable)对结构内部元素进行操作时使用的。我们再看看这个Foldable