Created
November 28, 2017 23:02
-
-
Save regadas/9d79ae5a78d0dc06a07d9fbf83a0f505 to your computer and use it in GitHub Desktop.
This file contains 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
/** | |
* 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