Skip to content

Instantly share code, notes, and snippets.

@telekosmos
Last active April 1, 2022 10:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save telekosmos/fc8a5b313070a39b8dcb4d0845d3c377 to your computer and use it in GitHub Desktop.
Save telekosmos/fc8a5b313070a39b8dcb4d0845d3c377 to your computer and use it in GitHub Desktop.
import cats.Monad
import cats.data.EitherT
import cats.effect.{IO, Sync}
import cats.implicits._
import cats.effect.unsafe.implicits.global
import scala.collection.mutable.ListBuffer
case class User(nick: String, points: Int)
// Algebra
trait UserRepositoryAlg[F[_]] {
def create(nick: String): F[Either[Error, User]]
def find(nick: String): F[Either[Error, User]]
def update(user: User): F[Either[Error, User]]
}
// Algebra(s) implementation - Interpreter
// UserRepositoryInterpreter is parametrized, but we require that F has typeclass Sync,
// which would allow us to delay effects with `Sync[F].delay`.
// Sync extends Monad, so we don't need to request is explicitly to be able to use for-comprehension
class UserRepositoryInterpreter[F[_]: Sync] extends UserRepositoryAlg[F] {
private val defaultUser = User("nick", 12)
val users: ListBuffer[User] = ListBuffer()
override def create(nick: String): F[Either[Error, User]] = {
val user = User(nick, 0)
users += user
for {
res <- Sync[F].delay(Right(user))
} yield res
}
override def find(nick: String): F[Either[Error, User]] = for {
// Finding user will be delayed, until we interpret and run our program.
// Delaying execution is useful for side-effecting effects, like requesting data from database, writting to console etc.
res <- Sync[F].delay(Either.fromOption(users.find(_.nick == nick), new Error("Couldn't find user")))
} yield res
// We can reuse find method from UserRepositoryInterpreter,
// but we have to wrap find in EitherT to access returned user
override def update(user: User): F[Either[Error, User]] = {
val result = for {
found <- EitherT(find(user.nick))
updated = found.copy(points = found.points + user.points)
} yield updated
result.value
}
}
// Program (not wrapped in any object to use in a REPL!!!)
class Pointer[F[_] : Monad](repo: UserRepositoryAlg[F]) {
def addPlayer(nick: String): EitherT[F, Error, User] = {
val res: EitherT[F, Error, User] = EitherT(repo.create(nick))
res
}
def addPoints(nick: String, points: Int): EitherT[F, Error, User] = {
for {
user <- EitherT(repo.find(nick))
updated <- EitherT(repo.update(user.copy(points = points)))
} yield updated
}
}
// At this point we define, that we want to use IO as our effect monad
val pointer = new Pointer[IO](new UserRepositoryInterpreter[IO]) // .addPoints("nick")
val computation: EitherT[IO, Error, User] = for {
user <- pointer.addPlayer("nikki")
res <- pointer.addPoints("nikki", 3)
} yield res
val computationValue: IO[Either[Error, User]] = computation.value
val result: Either[Error, User] = computationValue.unsafeRunSync()
result match {
case Right(user) => println(s"$user")
case Left(error) => println(s"ERR: $error")
}
println("EOS")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment