Skip to content

Instantly share code, notes, and snippets.

@regadas
Created November 28, 2017 23:02
Show Gist options
  • Save regadas/9d79ae5a78d0dc06a07d9fbf83a0f505 to your computer and use it in GitHub Desktop.
Save regadas/9d79ae5a78d0dc06a07d9fbf83a0f505 to your computer and use it in GitHub Desktop.
/**
* KVStore use case based on cats docs.
*
* trait class Free[F[_], A]
*
* Return from the computation with the given value.
*
* case class Pure[S[_], A](a: A) extends Free[S, A]
*
* Suspend the computation with the given suspension.
* case class Suspend[S[_], A](a: S[A]) extends Free[S, A]
*
* Call a subroutine and continue with the given function.
* case class FlatMapped[S[_], B, C](c: Free[S, C], f: C => Free[S, B]) extends Free[S, B]
*/
import cats.data.{EitherK, State}
import cats.free.Free
import scala.io.StdIn
/**
*
* // ~> Functor transformer
* `FunctionK[F[_], G[_]]` is a functor transformation from `F` to `G`
* in the same manner that function `A => B` is a morphism from values
* of type `A` to `B`.
*/
import cats.{Id, InjectK, ~>}
import scala.collection.mutable
import scala.language.higherKinds
object KVStore {
sealed trait KVStoreA[A]
case class Put[T](key: String, value: T) extends KVStoreA[Unit]
case class Get[T](key: String) extends KVStoreA[Option[T]]
case class Delete(key: String) extends KVStoreA[Unit]
type KVStore[A] = Free[KVStoreA, A]
type KVStoreState[A] = State[Map[String, Any], A]
val KVStoreState: State.type = State
object Ops {
def put[T](key: String, value: T): KVStore[Unit] =
Free.liftF[KVStoreA, Unit](Put[T](key, value))
def get[T](key: String): KVStore[Option[T]] =
Free.liftF[KVStoreA, Option[T]](Get[T](key))
def delete(key: String): KVStore[Unit] =
Free.liftF(Delete(key))
def update[T](key: String, f: T => T): KVStore[Unit] =
for {
vMaybe <- get[T](key)
_ <- vMaybe
.map { v =>
put[T](key, f(v))
}
.getOrElse(Free.pure(()))
} yield ()
}
object Compilers {
def inMemSimple: KVStoreA ~> Id =
new (KVStoreA ~> Id) {
val kvs = mutable.Map.empty[String, Any]
def apply[A](fa: KVStoreA[A]): Id[A] =
fa match {
case Put(key, value) =>
kvs(key) = value
()
case Get(key) =>
kvs.get(key).map(_.asInstanceOf[A])
case KVStore.Delete(key) =>
kvs.remove(key)
()
}
}
val inMem: KVStoreA ~> KVStoreState =
new (KVStoreA ~> KVStoreState) {
def apply[A](fa: KVStoreA[A]): KVStoreState[A] =
fa match {
case Put(key, value) =>
State.modify(_.updated(key, value))
case Get(key) =>
State.inspect(_.get(key).map(_.asInstanceOf[A]))
case Delete(key) =>
State.modify(_ - key)
}
}
}
}
object SimpleADT {
def exec: KVStore.KVStore[Option[Int]] =
for {
_ <- KVStore.Ops.put("foo", 2)
_ <- KVStore.Ops.update[Int]("foo", _ + 12)
n <- KVStore.Ops.get[Int]("foo")
} yield n
def run: Option[Int] =
exec.foldMap(KVStore.Compilers.inMem).runA(Map.empty).value
}
SimpleADT.run
// Let's combine a simple CLI to the above store
object CombinedADT {
/*
* InjectK is a type class providing an injection from type
* constructor `F` into type constructor `G`. An injection is a
* functor transformation `inj` which does not destroy any
* information: for every `ga: G[A]` there is at most one `fa: F[A]`
* such that `inj(fa) = ga`.
*/
class ReadWriter[F[_]](implicit I: InjectK[ReadWriter.ReadWrite, F]) {
def write[T](value: T): Free[F, Unit] =
Free.inject[ReadWriter.ReadWrite, F](ReadWriter.Write(value))
def read[T](value: T): Free[F, T] =
Free.inject[ReadWriter.ReadWrite, F](ReadWriter.Read(value))
}
object ReadWriter {
sealed trait ReadWrite[A]
case class Read[A](prompt: A) extends ReadWrite[A]
case class Write[A](msg: A) extends ReadWrite[Unit]
object Compilers {
val consoleInterpreter: ReadWrite ~> Id = new (ReadWrite ~> Id) {
def apply[A](i: ReadWrite[A]) = i match {
case Read(value) =>
println(value)
StdIn.readLine().asInstanceOf[A]
case Write(value) =>
Console.println(value)
}
}
}
implicit def readWrites[F[_]](
implicit I: InjectK[ReadWrite, F]): ReadWriter[F] = new ReadWriter[F]
}
class Store[F[_]](implicit I: InjectK[KVStore.KVStoreA, F]) {
def put[T](key: String, value: T): Free[F, Unit] =
KVStore.Ops.put(key, value).inject[F]
def get[T](key: String): Free[F, Option[T]] = KVStore.Ops.get(key).inject[F]
}
object Store {
implicit def store[F[_]](
implicit I: InjectK[KVStore.KVStoreA, F]): Store[F] =
new Store[F]
}
type App[A] = EitherK[KVStore.KVStoreA, ReadWriter.ReadWrite, A]
def exec(implicit I: ReadWriter[App], D: Store[App]): Free[App, Unit] = {
import D._
import I._
for {
key <- read("key:")
value <- read("value:")
_ <- put(key, value)
valueMaybe <- get[String](key)
_ <- write(valueMaybe)
} yield ()
}
val interpreter
: App ~> Id = KVStore.Compilers.inMemSimple or ReadWriter.Compilers.consoleInterpreter
def run = exec.foldMap(interpreter)
}
CombinedADT.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment