Created
January 9, 2018 18:29
-
-
Save agaro1121/969040886d64e0dc1ded053341631490 to your computer and use it in GitHub Desktop.
Free Monad vs Tagless Final
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
case class User(id: Long, name: String, age: Int) | |
class DatabaseError extends Throwable | |
case object ErrorFindingUser extends DatabaseError | |
case object ErrorUpdatingUser extends DatabaseError | |
case class ErrorDeletingUser(msg: String) extends DatabaseError |
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
import cats.free.Free | |
import cats.data.{EitherK, EitherT} | |
import cats.{InjectK, ~>} | |
import cats.instances.future._ | |
import scala.collection.mutable | |
import scala.concurrent.duration._ | |
import scala.concurrent.{Await, Future} | |
import scala.concurrent.ExecutionContext.Implicits.global | |
import DBFreeAlgebraT._ | |
import io.github.agaro1121.taglessfinal.free.Combined.DbAndConsoleAlgebra | |
import io.github.agaro1121.taglessfinal.free.ConsoleFreeAlgebraT.ConsoleFreeAlgebraTI | |
import io.github.agaro1121.taglessfinal.{DatabaseError, ErrorDeletingUser, ErrorFindingUser, User} | |
sealed trait ConsoleFreeAlgebraT[T] | |
case class PutLine[T](t: T) extends ConsoleFreeAlgebraT[Unit] | |
object ConsoleFreeAlgebraT { | |
type ConsoleAlgebra[T] = Free[ConsoleFreeAlgebraT, T] | |
class ConsoleFreeAlgebraTI[F[_]](implicit I: InjectK[ConsoleFreeAlgebraT, F]) { | |
def putLine[T](t: T): Free[F, Unit] = Free.inject[ConsoleFreeAlgebraT, F](PutLine(t)) | |
} | |
implicit def consoleFreeAlgebraTI[F[_]](implicit I: InjectK[ConsoleFreeAlgebraT, F]): ConsoleFreeAlgebraTI[F] = | |
new ConsoleFreeAlgebraTI[F] | |
val FutureInterpreter: (ConsoleFreeAlgebraT ~> Future) = new (ConsoleFreeAlgebraT ~> Future) { | |
override def apply[A](fa: ConsoleFreeAlgebraT[A]): Future[A] = | |
fa match { | |
case PutLine(t) => | |
Future.successful( | |
println(t) | |
).asInstanceOf[Future[A]] | |
} | |
} | |
} | |
sealed trait DBFreeAlgebraT[T] | |
case class Create[T](t: T) extends DBFreeAlgebraT[Boolean] | |
case class Read[T](id: Long) extends DBFreeAlgebraT[Either[DatabaseError, T]] | |
case class Delete[T](id: Long) extends DBFreeAlgebraT[Either[DatabaseError, Unit]] | |
object DBFreeAlgebraT { | |
type DBFreeAlgebra[T] = Free[DBFreeAlgebraT, T] | |
class DBFreeAlgebraTI[F[_]](implicit I: InjectK[DBFreeAlgebraT, F]) { | |
def create[T](t: T): Free[F, Boolean] = | |
Free.inject[DBFreeAlgebraT, F](Create(t)) | |
def read[T](id: Long): Free[F, Either[DatabaseError, User]] = | |
Free.inject[DBFreeAlgebraT, F](Read(id)) | |
def delete[T](id: Long): Free[F, Either[DatabaseError, Unit]] = | |
Free.inject[DBFreeAlgebraT, F](Delete(id)) | |
} | |
implicit def dBFreeAlgebraTI[F[_]](implicit I: InjectK[DBFreeAlgebraT, F]): DBFreeAlgebraTI[F] = | |
new DBFreeAlgebraTI[F] | |
val FutureInterpreter = new (DBFreeAlgebraT ~> Future) { | |
val users: mutable.Map[Long, User] = mutable.Map.empty | |
override def apply[A](fa: DBFreeAlgebraT[A]): Future[A] = | |
fa match { | |
case Create(user) => | |
val inserted = users.put(user.asInstanceOf[User].id, user.asInstanceOf[User]) | |
Future.successful(inserted.isEmpty || inserted.isDefined).map(_.asInstanceOf[A]) | |
case Read(id) => | |
Future.successful(users.get(id).toRight(ErrorFindingUser)).map(_.asInstanceOf[A]) | |
case Delete(id) => { | |
import cats.syntax.either._ | |
val deleted = users.remove(id) | |
Future.successful( | |
deleted.fold(ErrorDeletingUser(s"User with Id($id) was not there").asLeft[Unit])(_ => Right(())) | |
).map(_.asInstanceOf[A]) | |
} | |
} | |
} | |
} | |
object Combined { | |
type DbAndConsoleAlgebra[T] = EitherK[DBFreeAlgebraT, ConsoleFreeAlgebraT, T] | |
val FutureInterpreter: DbAndConsoleAlgebra ~> Future = | |
DBFreeAlgebraT.FutureInterpreter or ConsoleFreeAlgebraT.FutureInterpreter | |
} | |
class FreeUserRepo(implicit | |
DB: DBFreeAlgebraTI[Combined.DbAndConsoleAlgebra], | |
C: ConsoleFreeAlgebraTI[Combined.DbAndConsoleAlgebra]) { | |
def getUser(id: Long): Free[DbAndConsoleAlgebra, Either[DatabaseError, User]] = DB.read(id) | |
def addUser(user: User): Free[DbAndConsoleAlgebra, Boolean] = DB.create(user) | |
type DbAndConsoleAlgebraContainer[A] = Free[DbAndConsoleAlgebra, A] | |
def updateUser(user: User): Free[DbAndConsoleAlgebra, Either[DatabaseError, Boolean]] = (for { | |
userFromDB <- EitherT(getUser(user.id)) | |
_ <- EitherT.liftF(C.putLine(s"We found user($userFromDB)!!")) | |
successfullyAdded <- EitherT.liftF[DbAndConsoleAlgebraContainer, DatabaseError, Boolean](addUser(user)) | |
} yield successfullyAdded).value | |
} | |
object DBFreeAlgebraRunner extends App { | |
val repo = new FreeUserRepo | |
println(Await.result( | |
(for { | |
_ <- repo.addUser(User(1, "Bob", 31)) | |
dbErrorOrSuccessfullyUpdated <- repo.updateUser(User(1, "Bobby", 31)) | |
} yield dbErrorOrSuccessfullyUpdated).foldMap(Combined.FutureInterpreter), | |
1 second)) | |
} |
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
import cats.Monad | |
import cats.data.EitherT | |
import cats.instances.future._ | |
import scala.collection.mutable | |
import scala.concurrent.duration._ | |
import scala.concurrent.{Await, Future} | |
import scala.concurrent.ExecutionContext.Implicits.global | |
trait ConsoleAlgebra[F[_]] { | |
def putLine[T](t: T): F[Unit] | |
} | |
object ConsoleAlgebra { | |
implicit object FutureInterpreter extends ConsoleAlgebra[Future] { | |
override def putLine[T](t: T): Future[Unit] = Future.successful{ | |
println(t) | |
} | |
} | |
} | |
trait DatabaseAlgebra[F[_], T] { | |
def create(t: T): F[Boolean] | |
def read(id: Long): F[Either[DatabaseError, T]] | |
def delete(id: Long): F[Either[DatabaseError, Unit]] | |
} | |
object DatabaseAlgebra { | |
val FutureInterpreter: DatabaseAlgebra[Future, User] = | |
new DatabaseAlgebra[Future, User] { | |
val users: mutable.Map[Long, User] = mutable.Map.empty | |
override def create(user: User): Future[Boolean] = { | |
val inserted = users.put(user.id, user) | |
Future.successful(inserted.isEmpty || inserted.isDefined) | |
} | |
override def read(id: Long): Future[Either[DatabaseError, User]] = | |
Future.successful(users.get(id).toRight(ErrorFindingUser)) | |
override def delete(id: Long): Future[Either[DatabaseError, Unit]] = { | |
import cats.syntax.either._ | |
val deleted = users.remove(id) | |
Future.successful( | |
deleted.fold(ErrorDeletingUser(s"User with Id($id) was not there").asLeft[Unit])(_ => Right(()))) | |
} | |
} | |
} | |
class UserRepo[F[_]](DB: DatabaseAlgebra[F, User], | |
C: ConsoleAlgebra[F]) | |
(implicit M: Monad[F]) { | |
def getUser(id: Long): F[Either[DatabaseError, User]] = DB.read(id) | |
def addUser(user: User): F[Boolean] = DB.create(user) | |
def updateUser(user: User): F[Either[DatabaseError, Boolean]] = { | |
(for { | |
userFromDB <- EitherT(getUser(user.id)) | |
_ <- EitherT.liftF(C.putLine(s"We found user($userFromDB)!!")) | |
successfullyAdded <- EitherT.liftF[F, DatabaseError, Boolean](addUser(user)) | |
} yield successfullyAdded).value | |
} | |
} | |
object UserRepoRunner extends App { | |
val repo = new UserRepo(DatabaseAlgebra.FutureInterpreter, ConsoleAlgebra.FutureInterpreter) | |
println(Await.result( | |
(for { | |
_ <- repo.addUser(User(1, "Bob", 31)) | |
dbErrorOrSuccessfullyUpdated <- repo.updateUser(User(1, "Bobby", 31)) | |
} yield dbErrorOrSuccessfullyUpdated), | |
1 second)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment