Created
July 27, 2016 16:01
-
-
Save noelwelsh/8c85f6283e06ecad971d342cda7f77fa to your computer and use it in GitHub Desktop.
GCounter CRDT implementation in Scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scalaz.Monoid | |
import scalaz.syntax.monoid._ | |
import scalaz.syntax.traverse._ | |
import scalaz.std.map._ | |
import scalaz.std.anyVal._ | |
import scalaz.std.string._ | |
import scalaz.std.iterable._ | |
import scala.language.higherKinds | |
/* | |
/* A Bounded Semi-lattice is a Monoid that is commutative and idempotent */ | |
trait BoundedSemilattice[A] extends Monoid[A] | |
trait GCounter[F[_,_],K,V] { | |
def increment(f: F[K,V])(k: K, v: V)(implicit m: Monoid[V]): F[K,V] | |
def total(f: F[K,V])(implicit m: Monoid[V]): V | |
def merge(f1: F[K,V], f2: F[K,V])(implicit b: BoundedSemilattice[V]): F[K,V] | |
} | |
object GCounter { | |
implicit def mapGCounterInstance[K,V]: GCounter[Map,K,V] = | |
new GCounter[Map,K,V] { | |
def increment(f: Map[K,V])(k: K, v: V)(implicit m: Monoid[V]): Map[K,V] = | |
f + (k -> (f.getOrElse(k,mzero[V]) |+| v)) | |
def total(f: Map[K,V])(implicit m: Monoid[V]): V = | |
f.foldMap[V]() | |
def merge(f1: Map[K,V], f2: Map[K,V])(implicit b: BoundedSemilattice[V]): Map[K,V] = | |
f1 |+| f2 | |
} | |
def apply[F[_,_],K,V](implicit g: GCounter[F,K,V]) = g | |
} | |
object GCounterExample { | |
val g1 = Map("a" -> 7, "b" -> 3) | |
val g2 = Map("a" -> 2, "b" -> 5) | |
println(s"Merged: ${GCounter[Map,String,Int].merge(g1,g2)}") | |
println(s"Total: ${GCounter[Map,String,Int].total(g1)}") | |
} | |
*/ | |
/** A bounded semi-lattice is a monoid where the binary operation is idempotent and commutative. */ | |
trait BoundedSemiLattice[A] extends Monoid[A] | |
object BoundedSemiLattice { | |
implicit object intBoundedSemiLatticeInstance extends BoundedSemiLattice[Int] { | |
override def append(f1: Int, f2: ⇒ Int): Int = | |
f1 max f2 | |
override def zero: Int = Int.MinValue | |
} | |
} | |
final case class GCounter[V](map: Map[String,V]) { | |
def increment(id: String, amount: V)(implicit m: Monoid[V]): GCounter[V] = | |
GCounter(map + (id -> (map.getOrElse(id, mzero[V]) |+| amount))) | |
def total(implicit m: Monoid[V]): V = | |
map.values.foldMap() | |
def merge(that: GCounter[V])(implicit m: BoundedSemiLattice[V]): GCounter[V] = | |
GCounter(this.map |+| that.map) | |
} | |
object GCounterExample { | |
val g1 = GCounter(Map("a" -> 7, "b" -> 3)) | |
val g2 = GCounter(Map("a" -> 2, "b" -> 5)) | |
val merged = g1.merge(g2) | |
val total = g1.total | |
val updated = g1.increment("a", 2) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment