Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package io.github.themirrortruth
- object KVStore {
- def main(args: Array[String]): Unit = {
- import cats.free.Free
- import cats.{Id, ~>}
- //Strict language for working with abstract key-value stores defined as Algebraic Data Type (ADT)
- sealed trait KVStoreApi[Key, Value, Result]
- extends Product
- with Serializable
- //Encodes 'get by key' operation into the case class
- final case class Get[Key, Value](key: Key)
- extends KVStoreApi[Key, Value, Option[Value]]
- //Domain model
- final case class User(username: String, password: String)
- //Second, higher level language than 'KVStoreApi' that can be compiled into it
- sealed trait UserApi[Result] extends Product with Serializable
- //Encodes 'get user by username and password' into the case class
- final case class GetUser(username: String, password: String)
- extends UserApi[Option[User]]
- //lifts the 'Get by key' operation into Free Monad context, so we will be able to use 'for comprehension' syntax
- def get[Key, Value](
- key: Key): Free[KVStoreApi[Key, Value, ?], Option[Value]] =
- Free.liftF(Get[Key, Value](key): KVStoreApi[Key, Value, Option[Value]])
- //lifts the 'Get user by username and password' operation into Free Monad context, so we will be able to use 'for comprehension' syntax
- def getUser(userName: String,
- password: String): Free[UserApi, Option[User]] =
- Free.liftF(GetUser(userName, password))
- //UserApi to KVStoreApi interpreter, interprets operations from UserAPI ADT into the lower-level KvStoreAPI
- val userApiToKvInterpreter =
- new (UserApi ~> Free[KVStoreApi[String, String, ?], ?]) {
- override def apply[A](
- fa: UserApi[A]): Free[KVStoreApi[String, String, ?], A] = fa match {
- case GetUser(username, password) =>
- get[String, String](username).map {
- case Some(p) if p == password => Some(User(username, p))
- case _ => None
- }
- }
- }
- //Interprets operations from KVStoreAPI into the most lower-level Monad one.
- //Here is 'Id' monad is used, but it can be any Monad like 'Task' from Monix, 'IO' from cats-effect, etc.
- val userKvToIdInterpreter = new (KVStoreApi[String, String, ?] ~> Id) {
- private val usernamesToPasswords = Map("some_user" -> "some_password")
- override def apply[A](fa: KVStoreApi[String, String, A]): Id[A] = {
- fa match {
- case Get(key: String) => usernamesToPasswords.get(key)
- }
- }
- }
- println(
- //the operation is not running yet here, we just have built a description of program that can introspected later
- getUser("some_user", "some_password")
- //compiling description of the program into the KVStoreAPI operations
- .foldMap(userApiToKvInterpreter)
- //compiling operations from KVStoreAPI into the 'Id' monad, 'Id' monad is runnning eagerly, so it will print a result.
- //however, if we use something like Monix' Task, then we'd have to run it on some 'Scheduler' (analogue of ExecutionContext for Futures)
- .foldMap(userKvToIdInterpreter))
- }
- }
Add Comment
Please, Sign In to add comment