Skip to content

Instantly share code, notes, and snippets.

@noelwelsh
Created July 27, 2016 16:01
Show Gist options
  • Save noelwelsh/8c85f6283e06ecad971d342cda7f77fa to your computer and use it in GitHub Desktop.
Save noelwelsh/8c85f6283e06ecad971d342cda7f77fa to your computer and use it in GitHub Desktop.
GCounter CRDT implementation in Scala
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