Search code examples
scalamonadsscalazwriter-monad

context bound of scalar.Coyoneda.liftTF


Finished watching thought-provoking video "Composable application architecture with reasonably priced monads" from Rúnar Bjarnason, I started to write the examples provided in the video in Scalaz. I am having a little surprise when implementing the writer based interpreter for the Interact App. Here is the code,

sealed trait Interact[A]

case class Ask(prompt: String) extends Interact[String]

case class Tell(msg: String) extends Interact[Unit]

type W[A] = scalaz.Writer[Vector[String], A]

object ConsoleW extends (Interact ~> W) {
  def apply[A](i: Interact[A]) = i match {
    case Ask(prompt) =>
      println(prompt)
      Writer(Vector(prompt), readLine)
    case Tell(msg) =>
      println(msg)
      Writer(Vector.empty, ())
  }
}

When I tried to lift ConsoleW into an interpreter for the interact free monad, the compiler hinted me lack of context bound of Functor to W. I am quite surprised since Writer itself is a monad, the functor context bound should be given for free. Ok so I had to explicitly write a functor,

implicit val functor = new Functor[W] {
  def map[A, B](fa: Writer[Vector[String], A])(f: A => B) =
    fa.map(f)
}

which is pretty dumb... since I basically did nothing other than calling the map method of Writer itself. Now I can lift ConsoleW into an interpreter. However when I tried to foldMap the interact program with ConsoleW, the compiler again hinted the lack of Monad context bound for W! Ok, this is totally unexpected, let me spoon-feed you, Scalac...

val prg: Free[({type f[x] = Coyoneda[Interact, x]})#f, Unit] = ???

import scalaz.std.vector._

implicit val monad = new scalaz.Monad[W] {
  def point[A](a: => A) = Writer[Vector[String], A](Vector.empty, a)
  def bind[A, B](fa: Writer[Vector[String], A])(f: A => Writer[Vector[String], B]) = 
    fa.flatMap(f)
}

prg.foldMap(Coyoneda.liftTF(ConsoleW))

Now everything compiles, and the Writer Monad can record all the prompts into vector when the program runs. But this is unsatifying, I would like to create an interpreter with Writer as the underlying Monad without having to explicitly providing the proof that it is really a Functor and Monad. Any idea how this could work out?


Solution

  • Just need to import scalaz.std.vector._, and problem solved. I was impressed by how Id.Id could just work out as is, and forgot import of writer monad should be more complicated.