Solutions to Scala with Cats: Chapter 11
April 8, 2023These are my solutions to the exercises of chapter 11 of Scala with Cats.
Table of Contents
- Exercise 11.2.3: GCounter Implementation
- Exercise 11.3.2: BoundedSemiLattice Instances
- Exercise 11.3.3: Generic GCounter
- Exercise 11.4: Abstracting GCounter to a Type Class
- Exercise 11.5: Abstracting a Key Value Store
Exercise 11.2.3: GCounter Implementation
GCounter
can be implemented as follows:
final case class GCounter(counters: Map[String, Int]) {
def increment(machine: String, amount: Int): GCounter =
GCounter(counters.updatedWith(machine)(v => Some(v.getOrElse(0) + amount)))
def merge(that: GCounter): GCounter =
GCounter(that.counters.foldLeft(counters) { case (acc, (k, v)) =>
acc.updatedWith(k)(_.map(_ max v).orElse(Some(v)))
})
def total: Int =
counters.values.sum
}
Exercise 11.3.2: BoundedSemiLattice Instances
The following are possible implementations of BoundedSemiLattice
for Int
s
and Set
s. As described in the problem statement, we don’t need to model
non-negativity in the instance for Int
s:
import cats.kernel.CommutativeMonoid
trait BoundedSemiLattice[A] extends CommutativeMonoid[A] {
def combine(a1: A, a2: A): A
def empty: A
}
object BoundedSemiLattice {
implicit val intBoundedSemiLattice: BoundedSemiLattice[Int] =
new BoundedSemiLattice[Int] {
def combine(a1: Int, a2: Int): Int = a1 max a2
def empty: Int = 0
}
implicit def setBoundedSemiLattice[A]: BoundedSemiLattice[Set[A]] =
new BoundedSemiLattice[Set[A]] {
def combine(a1: Set[A], a2: Set[A]): Set[A] = a1 union a2
def empty: Set[A] = Set.empty[A]
}
}
Exercise 11.3.3: Generic GCounter
The following is a possible implementation of a generic GCounter
that uses
instances of CommutativeMonoid
and BoundedSemiLattice
:
import cats.kernel.CommutativeMonoid
import cats.syntax.foldable._
import cats.syntax.semigroup._
final case class GCounter[A](counters: Map[String, A]) {
def increment(machine: String, amount: A)(implicit m: CommutativeMonoid[A]): GCounter[A] =
GCounter(counters |+| Map(machine -> amount))
def merge(that: GCounter[A])(implicit b: BoundedSemiLattice[A]): GCounter[A] =
GCounter(counters |+| that.counters)
def total(implicit m: CommutativeMonoid[A]): A =
counters.values.toList.combineAll
}
Exercise 11.4: Abstracting GCounter to a Type Class
The implementation of an instance of the GCounter
type class for Map
closely
resembles the original GCounter
implementation:
import cats.kernel.CommutativeMonoid
import cats.syntax.foldable._
import cats.syntax.semigroup._
trait GCounter[F[_, _], K, V] {
def increment(f: F[K, V])(k: K, v: V)(implicit m: CommutativeMonoid[V]): F[K, V]
def merge(f1: F[K, V], f2: F[K, V])(implicit b: BoundedSemiLattice[V]): F[K, V]
def total(f: F[K, V])(implicit m: CommutativeMonoid[V]): V
}
object GCounter {
implicit def mapInstance[K, V]: GCounter[Map, K, V] =
new GCounter[Map, K, V] {
def increment(f: Map[K, V])(k: K, v: V)(implicit m: CommutativeMonoid[V]): Map[K, V] =
f |+| Map(k -> v)
def merge(f1: Map[K, V], f2: Map[K, V])(implicit b: BoundedSemiLattice[V]): Map[K, V] =
f1 |+| f2
def total(f: Map[K, V])(implicit m: CommutativeMonoid[V]): V =
f.values.toList.combineAll
}
}
Exercise 11.5: Abstracting a Key Value Store
The following is a possible implementation of an instance of the KeyValueStore
type class for Map
:
trait KeyValueStore[F[_, _]] {
def put[K, V](f: F[K, V])(k: K, v: V): F[K, V]
def get[K, V](f: F[K, V])(k: K): Option[V]
def values[K, V](f: F[K, V]): List[V]
def getOrElse[K, V](f: F[K, V])(k: K, default: V): V =
get(f)(k).getOrElse(default)
}
object KeyValueStore {
implicit val mapInstance: KeyValueStore[Map] =
new KeyValueStore[Map] {
def put[K, V](f: Map[K, V])(k: K, v: V): Map[K, V] =
f + (k -> v)
def get[K, V](f: Map[K, V])(k: K): Option[V] =
f.get(k)
def values[K, V](f: Map[K, V]): List[V] =
f.values.toList
}
}