Search code examples
scalamonadsscala-cats

Free Monad interpreter


I am following Chris Myers talk on Free Monads but when I implement the interpreter I get compile errors

package object example {
  import cats._
  import cats.data._
  import cats.free._
  import cats.implicits._

  case class Property(Id: Int, post: String)

  type Script[A] = Free[AppAction, A]

  sealed trait AppAction[A] {
    def lift: Script[A] = Free.liftF(this)
  }

  case class FetchById(propertyId: Int) extends AppAction[Property]

  case class Save(property: Property) extends AppAction[Unit]

  case class Log(message: String) extends AppAction[Unit]

  def fetchById(id: Int): Script[Property] = FetchById(id).lift
  def save(property: Property): Script[Unit] = Save(property).lift

  def log(message: String): Script[Unit] = Log(message).lift


  def updatePost(propertyId: Int, post: String): Script[Property] = {
    for {
      property <- fetchById(propertyId)
      p1 = property.copy(post = post)
      _ <- log("Updating")
      _ <- save(p1)
    } yield p1
  }

  object TestInterp extends (AppAction ~> Id) {
    def apply[A](fa: AppAction[A]): Id[A] = fa match {
      case FetchById(_) => Property(1, "2000")
      case Save(_) => ()
      case Log(msg) => println(msg)
    }

    def run[A](script: Script[A]) = script.foldMap(this)
  }

}

I have imported cats and specifically cats.Id but I get "Expression of type Property does not conform to cats.Id" in my test interpreter. I even explicitly wrapped result of fetchById in Id monad and it did not work.


Solution

  • In the for-expression

    p1 <- property.copy(post = post)
    

    is not a monadic bind step. It's just as simple assignment which defines a helper variable p1. This here seemed to work:

    import cats._
    import cats.data._
    import cats.free._
    import cats.implicits._
    
    case class Property(id: Int, post: String)
    
    
    sealed trait AppAction[A] {
      def lift: Script[A] = Free.liftF(this)
    }
    case class FetchById(propertyId: Int) extends AppAction[Property]
    case class Save(property: Property) extends AppAction[Unit]
    case class Log(message: String) extends AppAction[Unit]
    
    type Script[A] = Free[AppAction, A]
    
    def fetchById(id: Int): Script[Property] = FetchById(id).lift
    def save(property: Property): Script[Unit] = Save(property).lift
    def log(message: String): Script[Unit] = Log(message).lift
    
    def updatePost(propertyId: Int, post: String): Script[Unit] = {
      for {
        property <- fetchById(propertyId)
        p1 = property.copy(post = post)
        _ <- log("Updating")
        _ <- save(p1)
      } yield()
    }
    
    object TestInterp extends (AppAction ~> Id) {
      def apply[A](fa: AppAction[A]): Id[A] = fa match {
        case FetchById(_) => Property(1, "2000")
        case Save(_) => ()
        case Log(msg) => println(msg)
      }
    
      def run[A](script: Script[A]) = script.foldMap(this)
    }