Skip to content

Instantly share code, notes, and snippets.

@etorreborre
Last active September 7, 2016 03:22
Show Gist options
  • Save etorreborre/96519c69c045c649ac36eb7e15bdb39c to your computer and use it in GitHub Desktop.
Save etorreborre/96519c69c045c649ac36eb7e15bdb39c to your computer and use it in GitHub Desktop.
Using Eff on a "real" use case
/**
* We need to create bidirectional maps for items having both a name an an id
* When we collect items, we need to check that there are no duplicated names or ids for a given item
*/
// first let's define bi-directional maps
case class BiMap[K, V](keys: Map[K, V], values: Map[V, K]) {
def add(entry: BiMapEntry[K, V]): BiMap[K, V] =
BiMap(keys + (entry.key -> entry.value), values + (entry.value -> entry.key))
def getValue(k: K): Option[V] =
keys.get(k)
def getKey(v: V): Option[K] =
values.get(v)
}
import cats.std.list._
object BiMap {
def apply[K, V](entry: BiMapEntry[K, V]): BiMap[K, V] =
BiMap.empty[K, V].add(entry)
def empty[K, V]: BiMap[K, V] =
BiMap(Map(), Map())
implicit class createBiMapEntry[K](k: K) {
def <->[V](v: V): BiMapEntry[K, V] =
BiMapEntry[K, V](k, v)
}
def fromList[K, V](list: List[BiMapEntry[K, V]]) =
list.foldMap(e => BiMap.apply[K, V](e))
implicit def MonoidBiMap[K, V]: Monoid[BiMap[K, V]] = new Monoid[BiMap[K, V]] {
def empty = BiMap.empty[K, V]
def combine(m1: BiMap[K, V], m2: BiMap[K, V]): BiMap[K, V] =
BiMap(m1.keys ++ m2.keys, m1.values ++ m2.values)
}
}
case class BiMapEntry[K, V](key: K, value: V)
/**
* Now a stack of effects
*/
import cats.data._
import org.atnos.eff._
import Effects._
/**
* Update a bimap with a possible error
*/
import cats.std.option._
import cats.syntax.functor._
def updateMap[K](bimap: BM[K], key: K, item: Item): DuplicateError Xor BiMap[K, Item] =
Xor.fromOption(bimap.getValue(key).as(DuplicateError(s"$key is already present")), bimap.add(key <-> item)).swap
/**
* Update the state of the bi-maps
*/
import Eff._
import Effects._
import StateEffect._
import DisjunctionEffect._
import cats.syntax.flatMap._
// the effect stack
type BM[K] = BiMap[K, Item]
// our state will pair 2 bi-maps
type BMS = (BM[Name], BM[Id])
type SBM[A] = State[BMS, A]
type XorDup[A] = DuplicateError Xor A
type S = SBM |: XorDup |: NoEffect
// add an item and possibly update the maps
def addItem(item: Item): Eff[S, Unit] =
for {
maps <- get[S, BMS]
(nameMap, idMap) = maps
updatedNames <- fromXor[S, DuplicateError, BM[Name]](updateMap(nameMap, item.name, item))
updatedIds <- fromXor[S, DuplicateError, BM[Id]] (updateMap(idMap, item.id, item))
_ <- put[S, BMS]((updatedNames, updatedIds))
} yield ()
/**
* traverse a list of items
*/
import cats.syntax.traverse._
// note: no type annotation on traverse
def makeMaps(as: List[Item]): Eff[S, Unit] =
as.traverseU(addItem).as(())
/**
* Define a Monoid instance for a pair of Monoids
* (I couldn't find this in cats)
*/
object Monoidx {
implicit def PairMonoid[A : Monoid, B : Monoid]: Monoid[(A, B)] = new Monoid[(A, B)] {
def empty = (Monoid[A].empty, Monoid[B].empty)
def combine(m1: (A, B), m2: (A, B)) =
(Monoid[A].combine(m1._1, m2._1), Monoid[B].combine(m1._2, m2._2))
}
}
/**
* get the results
*/
val items = List(Item(Name("name1"), Id("id1")), Item(Name("name2"), Id("id2")))
run(runDisjunctionEither(
execZeroFirst(
makeMaps(items)
)
)
) must beRight[(BM[Name], BM[Id])]
/**
* The monad transformers version of that is actually not too bad
*/
import cats.std.list._
type S = (BiMap[Name, Item], BiMap[Id, Item])
def updateMap[K](bimap: BM[K], key: K, item: Item): DuplicateError Xor BiMap[K, Item] =
Xor.fromOption(bimap.getValue(key).as(DuplicateError(s"$key is already present")), bimap.add(key <-> item)).swap
// necessary implicit for StateT
implicit def m: Monad[StateT[DuplicateError Xor ?, S, ?]] =
StateT.stateTMonadState[DuplicateError Xor ?, S]
def addItem(item: Item): StateT[DuplicateError Xor ?, S, Unit] =
StateT[DuplicateError Xor ?, S, Unit] { case (names, ids) =>
for {
m1 <- update(names, _.name, item)
m2 <- update(ids, _.id, item)
} yield ((m1, m2), ())
}
// the type annotation is necessary on traverse
def makeMap(as: List[Item]): DuplicateError Xor (BiMap[Name, Item], BiMap[Id, Item]) =
as.traverse[StateT[DuplicateError Xor ?, S, ?], Unit](addItem).runS((BiMap.empty, BiMap.empty))
/**
* For the record we eventually went with a simple foldLeft(Xor.right(BiMap.empty)) { ... }
*
* Same functionality, but a lot less type annotations headache :-)
*
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment