Search code examples
scaladesign-patternsfunctional-programmingscalazalgebraic-data-types

Using Free with a non-functor in Scalaz


In the "FP in Scala" book there's this approach for using an ADT S as an abstract instruction set like

sealed trait Console[_]
case class PrintLine(msg: String) extends Console[Unit]
case object ReadLine extends Console[String]

and composing them with a Free[S, A] where S would later be translated to an IO monad. Can this be done with Scalaz's Free type? It seems that all run methods require a functor instance for S.


Solution

  • yes, you need a functor, but you can create one using Coyoneda.

    Coyoneda will turn any F[A] into a Coyoneda[F,A] and Coyoneda[F,A] is a functor.

    scala> type ConsoleCoyo[A] = Coyoneda[Console, A]
    defined type alias ConsoleCoyo
    

    Then scalaz's Free has a type alias just for this:

    /** A free monad over the free functor generated by `S` */
    type FreeC[S[_], A] = Free[({type f[x] = Coyoneda[S, x]})#f, A]
    

    So now We have a Free monad for console:

    scala> type ConsoleMonad[A] = Free.FreeC[ConsoleCoyo,A]
    defined type alias ConsoleMonad
    

    also you'll find handy that scalaz's Free has a function to lift a F[A] directly into a monad:

    /** A free monad over a free functor of `S`. */
    def liftFC[S[_], A](s: S[A]): FreeC[S, A] =
      liftFU(Coyoneda lift s)
    

    so, for example:

    scala> Free.liftFC(ReadLine)
    res1: scalaz.Free.FreeC[Console,String] = Suspend(scalaz.Coyoneda$$anon$22@360bb132)