I'm learning about the Free monad in Scala, and I've put together a simple example of algebra that I can lift into a Free monad using cats.
Here's my algebra
sealed trait ConsultationOp[A]
object consultation {
case class Create(c: Consultation) extends ConsultationOp[Unit]
case class Get(s: ConsultationId) extends ConsultationOp[Option[Consultation]]
}
And I'm able to use it like
def app = for {
c <- consultation.Create(Consultation("123", "A consultation"))
_ <- consultation.Get(c._id)
} yield ()
def interpreters = ConsultationInterpreter or UserInterpreter
app.foldMap(interpreters)
Where the lifting from ConsultationOp
to Free
is performed implicitly.
(there's a lot of details missing, the full working implementation is here: https://github.com/gabro/free-api)
So far so good, but what if I need to extract the optional value returned by consultation.Get
.
The first thing that comes to mind is a monad transformer, i.e. something like
def app = for {
c <- consultation.Create(Consultation("123", "A consultation")).liftM[OptionT]
d <- OptionT(consultation.Get(c._id))
_ <- doSomethingAConsultation(d)
} yield ()
but it looks ugly, and it doesn't feel right.
What's the glorified way - if any - of stacking monadic effects when using a Free monad?
The common way I see recurring in these cases is to use traverse, so you could change your code along the lines of:
import cats.syntax.traverse._
import cats.instances.option._
// ...
def app = for {
c <- consultation.Create(Consultation("123", "A consultation"))
d <- consultation.Get(c._id)
_ <- d.traverseU(doSomethingAConsultation(_))
} yield ()
Which, imho, is much cleaner than the monad transformer alternative.
Note that you could need some other import
and slightly modify the code, I didn't try it, but the concept is: use traverse.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With