標籤:
在地球上什麼是Monoid?? 定義:
monoid(么半群 譯註:參考附註1翻譯,下文中繼續使用英文名)是一個帶有二元運算(+)和一個單位元(譯註:原文為identity element)i使得對於任意x,x+i=i+x=x。注意它不像群(譯註:group,數學上翻譯為群),它不帶有逆元素。也可以說是帶有單位元的半群(semigroup)
Wow,沒什麼用。那先看一些例子然後重新看個簡單定義。。
https://blog.safaribooksonline.com/2013/05/15/monoids-for-programmers-a-scala-example/
1.整數和加法
結合律 => (a+b)+c == a+(b+c) 和 單位元=>0+n==n+0==n
1. 整數和乘法
結合律=>(a*b)c==a(b*c) 和單位元=>1*n==n*1==n
1.列表和關聯
結合律=>List(1,2)+(List(3,4)+List(5,6))==(List(1,2)+List(3,4))+List(5,6)==List(1,2,3,4,5,6)和單位元=>List(1)+List()==List()+List(1)==List(1)
這看起來任意二元操作都是Monoid.我們能否舉一些反例?
例如平均
avg(1,2)
avg(10,avg(20,30))!=avg(avg(10,20),30)
減法!
原來一個semigroup就是一個Monoid除了其不需要一個單位元,所以更具有包容性。
關鍵在於你想象二元操作結合性的要求,這意味著可以不在乎計算的順序!也意味著很容易並發的進行計算。
一個Monoid本質上是特定類型所遵循的協議。這是否給予我們任何線索在Scala中實現Monoids/Semigroups?
Typeclass實現Monoids
//It is up to the developer to enforce the associativity rule!!!trait SemiGroup[T]{ def op(a: T, b: T): T}trait Monoid[T] extends SemiGroup[T]{ def zero: T}123456789現在我們熟悉了怎麼在Scala中實現Monoid那麼我們能夠實現IntAdditionMonoid麼
object Monoids{ implicit object IntAdditionMonoid extends Monoid[Int]{ def op(a: Int, b: Int): Int = a + b def zeru: Int = 0 }}123456
好樣的。現在我們在哪可以使用呢?看如何在方法中使用例如reduce…
trait SemiGroup[T]{ def op(a: T, b: T): T}trait Monoid[T] extends SemiGroup[T]{ def zero: T}implicit object IntAdditionMonoid extends Monoid[Int]{ def op(a: Int, b: Int): Int = a + b def zero: Int = 0}val listA = List(1,3,5,6)def reduceWithMonoid[A](seq:Seq[A])(implicit ev:Monoid[A]): A = { seq.reduce(ev.op(_,_))}println(reduceWithMonoid(listA))12345678910111213141516171819
再定義更多然後看他們如何表現
trait SemiGroup[T]{ def op(a: T, b: T): T}trait Monoid[T] extends SemiGroup[T]{ def zero: T}implicit object IntAdditionMonoid extends Monoid[Int]{ def op(a: Int, b: Int): Int = a + b def zero: Int = 0}//We now have must use a class as type parameters are required due to the fact that tuples themselves have classes.//Here our goal is to define functionality for tuples that contain monoid abiding typesclass Tuple2SemiGroup[A,B]()(implicit sg1: SemiGroup[A], sg2: SemiGroup[B]) extends SemiGroup[(A,B)]{ def op(a: (A,B), b: (A,B)): (A,B) = (sg1.op(a._1, b._1), sg2.op(a._2, b._2))}//As we cannot make above an implicit class because that actually does something different (more on this with an aside about pimp my library pattern soon)//Well we can use another feature of implicits which are implicit conversions. This function provides logic on how to change a Tuple that contains Semigroups and return a SemiGroup of the tuple itselfimplicit def tuple2SemiGroup[A,B](implicit sg1: SemiGroup[A],sg2: SemiGroup[B]): SemiGroup[(A,B)] = { new Tuple2SemiGroup[A,B]()(sg1,sg2)}val listA = List((1,2),(3,4),(5,2),(6,9))def reduceWithMonoid[A](seq: Seq[A])(implicit ev: SemiGroup[A]): A = { seq.reduce(ev.op(_,_))}println(reduceWithMonoid(listA))123456789101112131415161718192021222324252627282930
···
看如何在monoid定義中包含聚集邏輯。事實上我們可以重定義集合對象的行為這意味著高重用和高擴充的代碼。再看一個例子,然後我們轉向spark.
在多看一個例子,Semigroups 可以很容易應用在合并2個maps關聯鍵並且對值求和。
trait SemiGroup[T]{ def op(a: T, b: T): T}trait Monoid[T] extends SemiGroup[T]{ def zero: T}implicit object IntAdditionMonoid extends Monoid[Int]{ def op(a: Int, b: Int): Int = a + b def zero: Int = 0}//Here we only need to assume that the values can form a SemiGroup as the keys are just being combined.class MapSemiGroup[K,V]()(implicit sg1: SemiGroup[V]) extends SemiGroup[Map[K,V]]{ //We are aggregating where the initial map is one of the maps and we loop through key values of other one and combine. //This way any keys that don‘t appear in the looping map are there already,all keys that appear in both are overwritten def op(iteratingMap: Map[K,V], startingMap: Map[K,V]): Map[K,V] = iteratingMap.aggregate(startingMap)({ (currentMap: Map[K,V], kv: (K,V)) => { val newValue: V = startingMap.get(kv._1).map(v => sg1.op(v, kv._2)).getOrElse(kv._2) currentMap + (kv._1 -> newValue) } }, //This is the combine part (if done in parallel, could have two different maps that need to be combined) this assumes that all keys are already combined.... { (mapOne: Map[K,V], mapTwo: Map[K,V]) => mapOne ++ mapTwo } )}//As we cannot make above an implicit class because that actually does something diferent (more on this with an aside about pimp my library pattern soon)//Well we can use another feature of implicits which are implicit conversions. This function provides logic on how to change a Tuple that contains Semigroups and return a SemiGroup of the tuple itselfimplicit def mapSemiGroup[K,V](implicit sg1: SemiGroup[V]): SemiGroup[Map[K,V]] = { new MapSemiGroup[K,V]()(sg1)}val mapA = Map("A" -> 1, "B" -> 2, "D" -> 5)val mapB = Map("A" -> 3, "C" -> 3, "D" -> 1)val mapC = Map("B" -> 10, "D" -> 3)def reduceWithMonoid[A](seq: Seq[A])(implicit ev: SemiGroup[A]): A = { seq.reduce(ev.op(_,_))}println(reduceWithMonoid(List(mapA, mapB, mapC)))12345678910111213141516171819202122232425262728293031323334353637383940414243
Pimp My Library(譯註:參考3)樣本和我們為什麼不用implicit classes
//An implicit class takes a constructor which is the class to be pimpd. You can then define methods etc. which will be "available" on that type as though it was native functionality!!!!implicit class PimpedString(s: String){ def pimpMyString(): String = s + " is pimped"}println("My String".pimpMyString())123456
Spark使用Pimp My Library模式來添加只在特定類型的RDDs上可用的特定方法。如:Key Value Pair RDDs
https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/rdd/PairRDDFunctions.scala
/*** Extra functions available on RDDs of (key, value) pairs through an implicit conversion.*/implicit class PairRDDFunctions[K, V](self: RDD[(K, V)])(implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null)extends Loggingwith SparkHadoopMapReduceUtilwith Serializable{}12345678910
這看起來你期望去設計你自己的monoid/semigroup庫。不要擔心,twitter已經做了並且使其可以用於Spark!(這意味著一切都是可序列化的)。他們還寫的讓其有效率(譯註:這句沒懂,原文:They have also written it in a way such that it performs very)
https://github.com/twitter/algebird
合在一起,Monoids與Spark
我們需要在spark中聚集RDDs時編寫很多函數,不幸的是,這些函數大致看上去是一樣的但是用一般方式很難編寫。使用Monoids是一種方式來達到目的,這是一個實際的例子:
//This is a call from an aggregation section that updates state with the HyperLogLog object val stateUniques = makeModelUniquesTime.updateStateByKey(updateTotalCountState[HLL]) //This is a call from an aggregation section that updates state with the Long val statePV = makeModelCountReduceWithTime.updateStateByKey(updateTotalCountState[Long])//This was originally implemented as tow methods, one for HLL and one for Long. With Monoids we can write a singel method that takes care of both cases.def updateTotalCountState[U](values: Seq[(BananaTimestamp, U)], state: Option[(BananaTimeStamp, U)])(implicit monoid: Monoid[U], ct: ClassTag[U]): Option[(BananaTimestamp, U)] = { val defaultState = (null, monoid.zero) values match { case Nil => Some(state.getOrElse(defaultState)) case _ => val hdT = values(0)._1 // The reduction logic is now contained in the monoid definitions as opposed to thest functions. We can instead distil this to what is takes to update state val v = values.map{case (_, a) => a}.reduce(monoid.plus) val stateReceived = state.getOrElse(defaultState) if(checkResetState(stateReceived._1, hdT)) Some((hdT, v)) else Some((hdT, monoid.plus(v, stateReceived._2))) } }123456789101112131415161718
原文連結:https://thewanderingmonad.wordpress.com/2015/05/17/introduction-to-monoids-and-semigroups-with-spark/
參考
1、monoid http://hongjiang.info/semigroup-and-monoid/
2、https://zh.wikipedia.org/wiki/%E5%B9%BA%E5%8D%8A%E7%BE%A4
3、http://www.ituring.com.cn/article/195776
Introduction to Monoids and Semigroups with Spark