Created
July 26, 2017 09:59
-
-
Save btlines/2403b10db3cd140423e4b8666c03b45f to your computer and use it in GitHub Desktop.
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 cats.{Id, Monad, Traverse, ~>} | |
import scala.concurrent.{Await, Future} | |
import scala.concurrent.duration.Duration | |
object TagLess extends App { | |
case class PurchaseOrderId(value: String) | |
case class ProductId(value: String) | |
case class LocationId(value: String) | |
trait Stock[F[_]] { | |
def poContent(poId: PurchaseOrderId): F[Map[ProductId, Int]] | |
def createStock(productId: ProductId, locationId: LocationId, quantity: Int): F[Unit] | |
def moveStock(productId: ProductId, source: LocationId, destination: LocationId, quantity: Int): F[Unit] | |
} | |
trait Inventory[F[_]] { | |
def increment(locationId: LocationId, productId: ProductId, quantity: Int): F[Unit] | |
def decrement(locationId: LocationId, productId: ProductId, quantity: Int): F[Unit] | |
} | |
trait Logging[F[_]] { | |
def error(message: String): F[Unit] | |
def info(message: String): F[Unit] | |
} | |
trait Storage[F[_]] { | |
def get[K, V](key: K): F[Option[V]] | |
def put[K, V](key: K, value: V): F[Unit] | |
} | |
val consoleLogger = new Logging[Id] { | |
override def error(message: String): Id[Unit] = | |
println(s"[ERROR] $message") | |
override def info(message: String): Id[Unit] = | |
println(s"[INFO ] $message") | |
} | |
val keyValueStore = new Storage[Future] { | |
var store = Map.empty[Any, Any] | |
override def get[K, V](key: K): Future[Option[V]] = | |
Future.successful(store.get(key).asInstanceOf[Option[V]]) | |
override def put[K, V](key: K, value: V): Future[Unit] = | |
Future.successful(store += key -> value) | |
} | |
// val keyValueStore = new Storage[Id] { | |
// var store = Map.empty[Any, Any] | |
// override def get[K, V](key: K): Id[Option[V]] = | |
// store.get(key).asInstanceOf[Option[V]] | |
// override def put[K, V](key: K, value: V): Id[Unit] = | |
// store += key -> value | |
// } | |
implicit class LiftTo[F[_], A](elem: F[A]) { | |
def liftTo[G[_]](implicit trans: F ~> G): G[A] = trans(elem) | |
} | |
def inventory[F[_], G[_], H[_]]( | |
logger: Logging[F], | |
store: Storage[G] | |
)(implicit | |
storeM: Monad[G], | |
transToG: F ~> G, | |
transToH: G ~> H | |
) = new Inventory[H] { | |
private def stockAt(locationId: LocationId, productId: ProductId): G[Int] = | |
storeM.map(store.get[(LocationId, ProductId), Int](locationId -> productId))(_ getOrElse 0) | |
override def increment(locationId: LocationId, productId: ProductId, quantity: Int): H[Unit] = { | |
import cats.Monad.ops._ | |
for { | |
_ <- logger.info(s"Add $quantity $productId at $locationId").liftTo[G] | |
existing <- stockAt(locationId, productId) | |
_ <- store.put(locationId -> productId, existing + quantity) | |
} yield () | |
}.liftTo[H] | |
override def decrement(locationId: LocationId, productId: ProductId, quantity: Int): H[Unit] = { | |
import cats.Monad.ops._ | |
for { | |
_ <- logger.info(s"Remove $quantity $productId from $locationId").liftTo[G] | |
existing <- stockAt(locationId, productId) | |
_ <- store.put(locationId -> productId, existing - quantity) | |
} yield () | |
}.liftTo[H] | |
} | |
def stock[F[_], G[_]](inventory: Inventory[F])(implicit | |
inventoryM: Monad[F], | |
stockM: Monad[G], | |
transToG: F ~> G | |
): Stock[G] = new Stock[G] { | |
import cats.Monad.ops._ | |
override def poContent(poId: PurchaseOrderId): G[Map[ProductId, Int]] = { | |
stockM.pure(Map(ProductId("Mars") -> 200, ProductId("Milkyway") -> 150, ProductId("Galaxy") -> 100)) | |
} | |
override def createStock(productId: ProductId, locationId: LocationId, quantity: Int): G[Unit] = { | |
inventory.increment(locationId, productId, quantity).liftTo[G] | |
} | |
override def moveStock(productId: ProductId, source: LocationId, destination: LocationId, quantity: Int): G[Unit] = { | |
inventory | |
.decrement(source, productId, quantity) | |
.flatMap(_ => inventory.increment(source, productId, quantity)) | |
.liftTo[G] | |
} | |
} | |
def inbound[F[_], G[_]](logger: Logging[F], stock: Stock[G])(poId: PurchaseOrderId)(implicit stockM: Monad[G], transToG: F ~> G, traverse: Traverse[List]): G[Unit] = { | |
import cats.Monad.ops._ | |
import cats.Traverse.ops._ | |
for { | |
_ <- logger.info(s"Inbounding $poId").liftTo[G] | |
content <- stock.poContent(poId) | |
_ <- stockM.traverse(content.toList) { | |
case (productId, quantity) => | |
stock.createStock(productId, LocationId("dock"), quantity) | |
} | |
_ <- stockM.traverse(content.toList) { | |
case (productId, quantity) => | |
stock.moveStock(productId, LocationId("dock"), LocationId("warehouse"), quantity) | |
} | |
} yield () | |
} | |
import cats.instances.future._ | |
implicit def identityTransform[T[_]] = new (T ~> T) { | |
override def apply[A](fa: T[A]): T[A] = fa | |
} | |
implicit def monadTransform[T[_] : Monad] = new (Id ~> T) { | |
override def apply[A](a: Id[A]): T[A] = Monad[T].pure(a) | |
} | |
implicit val executor = scala.concurrent.ExecutionContext.global | |
val inventoryService: Inventory[Future] = inventory(consoleLogger, keyValueStore) | |
val stockService: Stock[Future] = stock(inventoryService) | |
import cats.instances.list._ | |
val res = inbound(consoleLogger, stockService)(PurchaseOrderId("po-1")) | |
Await.result(res, Duration.Inf) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment