20

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?

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • 1
    There's a discussion related to this [here](https://www.reddit.com/r/scala/comments/5p3fc3/free_monads_in_scala_web_stack_part_i/dco5yqy/). The gist is that using Free doesn't free you from handling the value `A` in `ConsultationOp`. There are libraries like [freek](https://github.com/ProjectSeptemberInc/freek) and [eff](https://github.com/atnos-org/eff) which solves this issue more elegantly. – Jacob Wang Jan 27 '17 at 13:05

1 Answers1

3

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.

lambdista
  • 1,850
  • 9
  • 16
  • thank you! Is this common practice (to your experience) or is it maybe more common to move the monad stacking to the interpreter and have a natural transformation from `ConsultationOp` to (e.g.) `Future[Option]` there? – Gabriele Petronella Feb 27 '17 at 09:34
  • In my, small to tell the truth, experience I've always solved this type of problems using `traverse`. However moving the monad stacking to the interpreter is an option and some libs do just that, e.g. freestyle: https://github.com/47deg/freestyle/blob/master/freestyle-effects/shared/src/main/scala/effects/option.scala. Unluckily, I hadn't have the time to look at it thoroughly yet. – lambdista Mar 03 '17 at 08:03