Using Freasy and cats Free Monads to model a domain.
Freasy macro allows for very concise code:
-
Define DSL:
@free trait VideoRental { sealed trait DSL[A] type VideoRentalF[A] = Free[DSL, A] def addInventory(movie: Movie, qty: Int): VideoRentalF[Set[DVD]] def searchForDVD(movie: Movie): VideoRentalF[Option[DVD]] def rentDVD(dvd: DVD): VideoRentalF[Unit] def returnDVD(dvd: DVD): VideoRentalF[Unit] }
-
Implement service:
override def interpreter() = new VideoRental.Interp[ErrorOr] { override def addInventory(movie: Movie, qty: Int): ErrorOr[Set[DVD]] = ... override def searchForDVD(movie: Movie): ErrorOr[Option[DVD]] = ... override def rentDVD(dvd: DVD): ErrorOr[Unit] = ... override def returnDVD(dvd: DVD): ErrorOr[Unit] = ... }
Using Freek
Combine two different Free algebras (as long as they use same return type):
type PRG = Logging.DSL :|: VideoRental.DSL :|: NilDSL
val PRG = DSL.Make[PRG]
val VS = VideoRental.DSL
val LOG = Logging.DSL
// Create program
val program = for {
_ <- LOG.Info(s"Adding $movie").freek[PRG]
dvds <- VS.AddInventory(movie, qty).freek[PRG]
dvd = dvds.head
_ <- LOG.Info(s"Renting $dvd").freek[PRG]
_ <- VS.RentDVD(dvd).freek[PRG]
_ <- LOG.Info(s"Returning $dvd").freek[PRG]
res <- VS.ReturnDVD(dvd).freek[PRG]
} yield res
// Combine interpreters for VideoRental and Logging
val combinedInterpreter: Interpreter[PRG.Cop, ErrorOr] = StdoutLogging().interpreter :&: InMemory().interpreter
val result = program.interpret(combinedInterpreter)