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 evaluate 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: [email protected]@[Int,scalaz.Tags.Multiplication] = 3 6 Tags.Conjunction(true) |+| Tags.Conjunction(false)//> res4: [email protected]@[Boolean,scalaz.Tags.Conjunction] = false 7 Tags.Disjunction(true) |+| Tags.Disjunction(false)//> res5: [email protected]@[Boolean,scalaz.Tags.Disjunction] = true 8 Monoid[Boolean @@ Tags.Conjunction].zero //> res6: [email protected]@[Boolean,scalaz.Tags.Conjunction] = true 9 Monoid[Boolean @@ Tags.Disjunction].zero //> res7: [email protected]@[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 typeclass:scalaz/Foldable.scala
1 trait Foldable[F[_]] { self => 2 //// 3 import collection.generic.CanBuildFrom 4 import collection.immutable.IndexedSeq 5 6 /** Map each element of the structure to a [[scalaz.Monoid]], and combine the results. */ 7 def foldMap[A,B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B 8 /** As `foldMap` but returning `None` if the foldable is empty and `Some` otherwise */ 9 def foldMap1Opt[A,B](fa: F[A])(f: A => B)(implicit F: Semigroup[B]): Option[B] = { 10 import std.option._ 11 foldMap(fa)(x => some(f(x))) 12 } 13 14 /**Right-associative fold of a structure. */ 15 def foldRight[A, B](fa: F[A], z: => B)(f: (A, => B) => B): B 16 ...
Foldable typeclass提供了许多注入方法支持折叠操作: scalaz/syntax/FoldableSyntax.scala
1 final class FoldableOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Foldable[F]) extends Ops[F[A]] { 2 //// 3 import collection.generic.CanBuildFrom 4 import Leibniz.=== 5 import Liskov.<~< 6 7 final def foldMap[B: Monoid](f: A => B = (a: A) => a): B = F.foldMap(self)(f) 8 final def foldMap1Opt[B: Semigroup](f: A => B = (a: A) => a): Option[B] = F.foldMap1Opt(self)(f) 9 final def foldRight[B](z: => B)(f: (A, => B) => B): B = F.foldRight(self, z)(f) 10 final def foldMapRight1Opt[B](z: A => B)(f: (A, => B) => B): Option[B] = F.foldMapRight1Opt(self)(z)(f) 11 final def foldRight1Opt(f: (A, => A) => A): Option[A] = F.foldRight1Opt(self)(f) 12 final def foldLeft[B](z: B)(f: (B, A) => B): B = F.foldLeft(self, z)(f) 13 final def foldMapLeft1Opt[B](z: A => B)(f: (B, A) => B): Option[B] = F.foldMapLeft1Opt(self)(z)(f) 14 final def foldLeft1Opt(f: (A, A) => A): Option[A] = F.foldLeft1Opt(self)(f) 15 final def foldRightM[G[_], B](z: => B)(f: (A, => B) => G[B])(implicit M: Monad[G]): G[B] = F.foldRightM(self, z)(f) 16 final def foldLeftM[G[_], B](z: B)(f: (B, A) => G[B])(implicit M: Monad[G]): G[B] = F.foldLeftM(self, z)(f) 17 final def foldMapM[G[_] : Monad, B : Monoid](f: A => G[B]): G[B] = F.foldMapM(self)(f) 18 final def fold(implicit A: Monoid[A]): A = F.fold(self)(A) 19 final def foldr[B](z: => B)(f: A => (=> B) => B): B = F.foldr(self, z)(f) 20 final def foldr1Opt(f: A => (=> A) => A): Option[A] = F.foldr1Opt(self)(f) 21 final def foldl[B](z: B)(f: B => A => B): B = F.foldl(self, z)(f) 22 final def foldl1Opt(f: A => A => A): Option[A] = F.foldl1Opt(self)(f) 23 final def foldrM[G[_], B](z: => B)(f: A => ( => B) => G[B])(implicit M: Monad[G]): G[B] = F.foldrM(self, z)(f) 24 final def foldlM[G[_], B](z: B)(f: B => A => G[B])(implicit M: Monad[G]): G[B] = F.foldlM(self, z)(f) 25 final def length: Int = F.length(self) 26 final def index(n: Int): Option[A] = F.index(self, n) 27 final def indexOr(default: => A, n: Int): A = F.indexOr(self, default, n) 28 final def sumr(implicit A: Monoid[A]): A = F.foldRight(self, A.zero)(A.append) 29 final def suml(implicit A: Monoid[A]): A = F.foldLeft(self, A.zero)(A.append(_, _)) 30 final def toList: List[A] = F.toList(self) 31 final def toVector: Vector[A] = F.toVector(self) 32 final def toSet: Set[A] = F.toSet(self) 33 final def toStream: Stream[A] = F.toStream(self) 34 final def toIList: IList[A] = F.toIList(self) 35 final def toEphemeralStream: EphemeralStream[A] = F.toEphemeralStream(self) 36 final def to[G[_]](implicit c: CanBuildFrom[Nothing, A, G[A]]) = F.to[A, G](self) 37 final def all(p: A => Boolean): Boolean = F.all(self)(p) 38 final def ∀(p: A => Boolean): Boolean = F.all(self)(p) 39 final def allM[G[_]: Monad](p: A => G[Boolean]): G[Boolean] = F.allM(self)(p) 40 final def anyM[G[_]: Monad](p: A => G[Boolean]): G[Boolean] = F.anyM(self)(p) 41 final def any(p: A => Boolean): Boolean = F.any(self)(p) 42 final def ∃(p: A => Boolean): Boolean = F.any(self)(p) 43 final def count: Int = F.count(self) 44 final def maximum(implicit A: Order[A]): Option[A] = F.maximum(self) 45 final def maximumOf[B: Order](f: A => B): Option[B] = F.maximumOf(self)(f) 46 final def maximumBy[B: Order](f: A => B): Option[A] = F.maximumBy(self)(f) 47 final def minimum(implicit A: Order[A]): Option[A] = F.minimum(self) 48 final def minimumOf[B: Order](f: A => B): Option[B] = F.minimumOf(self)(f) 49 final def minimumBy[B: Order](f: A => B): Option[A] = F.minimumBy(self)(f) 50 final def longDigits(implicit d: A <:< Digit): Long = F.longDigits(self) 51 final def empty: Boolean = F.empty(self) 52 final def element(a: A)(implicit A: Equal[A]): Boolean = F.element(self, a) 53 final def splitWith(p: A => Boolean): List[NonEmptyList[A]] = F.splitWith(self)(p) 54 final def selectSplit(p: A => Boolean): List[NonEmptyList[A]] = F.selectSplit(self)(p) 55 final def collapse[X[_]](implicit A: ApplicativePlus[X]): X[A] = F.collapse(self) 56 final def concatenate(implicit A: Monoid[A]): A = F.fold(self) 57 final def intercalate(a: A)(implicit A: Monoid[A]): A = F.intercalate(self, a) 58 final def traverse_[M[_]:Applicative](f: A => M[Unit]): M[Unit] = F.traverse_(self)(f) 59 final def traverseU_[GB](f: A => GB)(implicit G: Unapply[Applicative, GB]): G.M[Unit] = 60 F.traverseU_[A, GB](self)(f)(G) 61 final def traverseS_[S, B](f: A => State[S, B]): State[S, Unit] = F.traverseS_(self)(f) 62 final def sequence_[G[_], B](implicit ev: A === G[B], G: Applicative[G]): G[Unit] = F.sequence_(ev.subst[F](self))(G) 63 final def sequenceS_[S, B](implicit ev: A === State[S,B]): State[S,Unit] = F.sequenceS_(ev.subst[F](self)) 64 def sequenceF_[M[_],B](implicit ev: F[A] <~< F[Free[M,B]]): Free[M, Unit] = F.sequenceF_(ev(self)) 65 final def msuml[G[_], B](implicit ev: A === G[B], G: PlusEmpty[G]): G[B] = F.foldLeft(ev.subst[F](self), G.empty[B])(G.plus[B](_, _)) 66 //// 67 }
这简直就是一个完整的函数库嘛。scalaz为大多数标准库中的集合类型提供了Foldable实例,也就是说大多数scala集合类型都支持这么一堆折叠操作函数。我还看不到任何需要去自定义集合类型,标准库的集合类型加上Foldable typeclass应该足够用了。
在Foldable typeclass中比较重要的函数就是foldMap了:
1 trait Foldable[F[_]] { self => 2 //// 3 import collection.generic.CanBuildFrom 4 import collection.immutable.IndexedSeq 5 6 /** Map each element of the structure to a [[scalaz.Monoid]], and combine the results. */ 7 def foldMap[A,B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B
首先,foldMap需要Monoid[B]实例来实现。用List来举例:List trait 继承了Traverse:scalaz/std/List.scala
1 trait ListInstances extends ListInstances0 { 2 implicit val listInstance = new Traverse[List] with MonadPlus[List] with Zip[List] with Unzip[List] with Align[List] with IsEmpty[List] with Cobind[List] { 3 ...
在Traverse typeclass里定义了Foldable实例:scalaz/Traverse.scala
1 def foldLShape[A,B](fa: F[A], z: B)(f: (B,A) => B): (B, F[Unit]) = 2 runTraverseS(fa, z)(a => State.modify(f(_, a))) 3 4 override def foldLeft[A,B](fa: F[A], z: B)(f: (B,A) => B): B = foldLShape(fa, z)(f)._1 5 6 def foldMap[A,B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B = foldLShape(fa, F.zero)((b, a) => F.append(b, f(a)))._1 7 8 override def foldRight[A, B](fa: F[A], z: => B)(f: (A, => B) => B) = 9 foldMap(fa)((a: A) => (Endo.endo(f(a, _: B)))) apply z 10 ...
这个foldMap就是一个游览可折叠结构的函数。在游览过程中用Monoid append对结构中元素进行操作。值得注意的是这个f: A => B参数:这个函数是用来在append操作之前先对内部元素进行一次转变(transform):
1 List(1,2,3) foldMap {x => x} //> res18: Int = 6 2 List(1,2,3) foldMap {x => (x + 3).toString} //> res19: String = 456 变成String操作
我们试着用一些实际的例子来示范Monoid的用法。上面提到Monoid在可折叠数据结构里的元素连续处理有着很好的应用,我们先试一个例子:确定一个可折叠数据结构F[A]中的元素A是否排序的:
def ordered(xs: List[Int]): Boolean //判断xs是否按序排列
由于我们必须游览List xs,所以用Monoid对元素Int进行判断操作是可行的方法。我们先设计一个对比数据结构:
Option[(min: Int, max: Int. ordered: Boolean)], 它记录了当前元素的状态,包括最小,最大,是否排序的:
1 /判断xs是否是排序的 2 def ordered(xs: List[Int]): Boolean = { 3 val monoid = new Monoid[Option[(Int,Int,Boolean)]] { //对类型Option[(Int,Int,Boolean)]定义一个Monoid实例 4 def zero = None 5 def append(a1: Option[(Int,Int,Boolean)], a2: => Option[(Int,Int,Boolean)]) = //对连续两个元素进行对比操作 6 (a1,a2) match { 7 case (x,None) => x 8 case (None,x) => x //保留不为None的状态 9 case (Some((min1,max1,ord1)),Some((min2,max2,ord2))) => //如果max1 <= min2状态即为true 10 Some((min1 min min2, max1 max max2, ord1 && ord2 && (max1 <= min2))) //更新min,max和ord 11 } 12 } //我们需要把元素转换成Option((Int,Int,Boolean)) 13 (xs.foldMap(i => Option((i, i, true)))(monoid)).map(_._3) getOrElse(true) 14 } //> ordered: (xs: List[Int])Boolean 15 16 ordered(List(1,2,12,34)) //> res21: Boolean = true 17 ordered(List(1,2,34,23)) //> res22: Boolean = false
注意这个i => Option((i,i,true)) 转换(transform)。
由于Monoid是种极简单的类型,所以很容易对Monoid进行组合。Monoid组合产生的结果还是Monoid,并且用起来可以更方便:
1 def productMonoid[A,B](ma: Monoid[A], mb: Monoid[B]): Monoid[(A,B)] = new Monoid[(A,B)] { 2 def zero = (ma.zero, mb.zero) 3 def append(x: (A,B), y: => (A,B)): (A,B) = (ma.append(x._1, y._1), mb.append(x._2, y._2)) 4 } //> productMonoid: [A, B](ma: scalaz.Monoid[A], mb: scalaz.Monoid[B])scalaz.Mon 5 //| oid[(A, B)] 6 val pm = productMonoid(Monoid[Int],Monoid[List[Int]]) 7 //> pm : scalaz.Monoid[(Int, List[Int])] = Exercises.monoid$$anonfun$main$1$$a 8 //| [email protected]
以上的pm就是两个Monoid的组合,结果是一个tuple2Monoid。我们可以使用这个tuple2Monoid对可折叠数据结构中元素进行并行操作。比如我们可以在游览一个List[Int]时同时统计长度(list length)及乘积(product):
1 val intMultMonoid = new Monoid[Int] { 2 def zero = 1 3 def append(a1: Int, a2: => Int): Int = a1 * a2 4 } //> intMultMonoid : scalaz.Monoid[Int] = Exercises.monoid$$anonfun$main$1$$ano 5 //| [email protected] 6 def productMonoid[A,B](ma: Monoid[A], mb: Monoid[B]): Monoid[(A,B)] = new Monoid[(A,B)] { 7 def zero = (ma.zero, mb.zero) 8 def append(x: (A,B), y: => (A,B)): (A,B) = (ma.append(x._1, y._1), mb.append(x._2, y._2)) 9 } //> productMonoid: [A, B](ma: scalaz.Monoid[A], mb: scalaz.Monoid[B])scalaz.Mon 10 //| oid[(A, B)] 11 val pm = productMonoid(Monoid[Int @@ Tags.Multiplication],Monoid[Int]) 12 //> pm : scalaz.Monoid[([email protected]@[Int,scalaz.Tags.Multiplication], Int)] = Exe 13 //| [email protected] 14 List(1,2,3,4,6).foldMap(i => (i, 1))(productMonoid(intMultMonoid,Monoid[Int])) 15 //> res23: (Int, Int) = (144,5)
我们再来一个合并多层map的Monoid:
1 def mapMergeMonoid[K,V](V: Monoid[V]): Monoid[Map[K, V]] = 2 new Monoid[Map[K, V]] { 3 def zero = Map[K,V]() 4 def append(a: Map[K, V], b: => Map[K, V]) = 5 (a.keySet ++ b.keySet).foldLeft(zero) { (acc,k) => 6 acc.updated(k, V.append(a.getOrElse(k, V.zero), 7 b.getOrElse(k, V.zero))) 8 } 9 } //> mapMergeMonoid: [K, V](V: scalaz.Monoid[V])scalaz.Monoid[Map[K,V]] 10 11 val M: Monoid[Map[String, Map[String, Int]]] = mapMergeMonoid(mapMergeMonoid(Monoid[Int])) 12 //> M : scalaz.Monoid[Map[String,Map[String,Int]]] = Exercises.monoid$$anonfun 13 //| [email protected] 14 val m1 = Map("o1" -> Map("i1" -> 1, "i2" -> 2)) //> m1 : scala.collection.immutable.Map[String,scala.collection.immutable.Map[ 15 //| String,Int]] = Map(o1 -> Map(i1 -> 1, i2 -> 2)) 16 val m2 = Map("o1" -> Map("i2" -> 3)) //> m2 : scala.collection.immutable.Map[String,scala.collection.immutable.Map[ 17 //| String,Int]] = Map(o1 -> Map(i2 -> 3)) 18 val m3 = M.append(m1, m2) //> m3 : Map[String,Map[String,Int]] = Map(o1 -> Map(i1 -> 1, i2 -> 5))
我们可以用这个组合成的M的append操作进行map的深度合并。m1,m2合并后:Map(o1->Map("i1"->1,"i2" -> 5))。
我们还可以用这个Monoid来统计一段字串内字符发生的频率:
1 def frequencyMap[A](as: List[A]): Map[A, Int] = 2 as.foldMap((a: A) => Map(a -> 1))(mapMergeMonoid[A, Int](Monoid[Int])) 3 //> frequencyMap: [A](as: List[A])Map[A,Int] 4 frequencyMap("the brown quik fox is running quikly".toList) 5 //> res24: Map[Char,Int] = Map(e -> 1, s -> 1, x -> 1, n -> 4, y -> 1, t -> 1, 6 //| u -> 3, f -> 1, i -> 4, -> 6, q -> 2, b -> 1, g -> 1, l -> 1, h -> 1, r - 7 //| > 2, w -> 1, k -> 2, o -> 2)
我们现在可以体会到Monoid必须在可折叠数据结构(Foldable)内才能正真发挥作用。