diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index f16f533359..336e27d6ff 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -174,6 +174,21 @@ object Free { def suspend[F[_], A](value: => Free[F, A]): Free[F, A] = pure(()).flatMap(_ => value) + /** + * a FunctionK, suitable for composition, which calls compile + */ + def compile[F[_], G[_]](fk: FunctionK[F, G]): FunctionK[Free[F, ?], Free[G, ?]] = + new FunctionK[Free[F, ?], Free[G, ?]] { + def apply[A](f: Free[F, A]): Free[G, A] = f.compile(fk) + } + /** + * a FunctionK, suitable for composition, which calls foldMap + */ + def foldMap[F[_], M[_]: Monad](fk: FunctionK[F, M]): FunctionK[Free[F, ?], M] = + new FunctionK[Free[F, ?], M] { + def apply[A](f: Free[F, A]): M[A] = f.foldMap(fk) + } + /** * This method is used to defer the application of an Inject[F, G] * instance. The actual work happens in diff --git a/free/src/main/scala/cats/free/FreeT.scala b/free/src/main/scala/cats/free/FreeT.scala index afbdb7717d..44fd32a053 100644 --- a/free/src/main/scala/cats/free/FreeT.scala +++ b/free/src/main/scala/cats/free/FreeT.scala @@ -3,6 +3,8 @@ package free import scala.annotation.tailrec +import cats.arrow.FunctionK + /** * FreeT is a monad transformer for Free monads over a Functor S * @@ -27,7 +29,7 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { * Changes the underlying `Monad` for this `FreeT`, ie. * turning this `FreeT[S, M, A]` into a `FreeT[S, N, A]`. */ - def hoist[N[_]](mn: M ~> N): FreeT[S, N, A] = + def hoist[N[_]](mn: FunctionK[M, N]): FreeT[S, N, A] = step match { case e @ FlatMapped(_, _) => FlatMapped(e.a.hoist(mn), e.f.andThen(_.hoist(mn))) @@ -35,11 +37,14 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { Suspend(mn(m)) } + @deprecated("Use compile", "0.8.0") + def interpret[T[_]](st: FunctionK[S, T])(implicit M: Functor[M]): FreeT[T, M, A] = compile(st) + /** Change the base functor `S` for a `FreeT` action. */ - def interpret[T[_]](st: S ~> T)(implicit M: Functor[M]): FreeT[T, M, A] = + def compile[T[_]](st: FunctionK[S, T])(implicit M: Functor[M]): FreeT[T, M, A] = step match { case e @ FlatMapped(_, _) => - FlatMapped(e.a.interpret(st), e.f.andThen(_.interpret(st))) + FlatMapped(e.a.compile(st), e.f.andThen(_.compile(st))) case Suspend(m) => Suspend(M.map(m)(_.left.map(s => st(s)))) } @@ -48,7 +53,7 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { * Runs to completion, mapping the suspension with the given transformation * at each step and accumulating into the monad `M`. */ - def foldMap(f: S ~> M)(implicit M: Monad[M]): M[A] = { + def foldMap(f: FunctionK[S, M])(implicit M: Monad[M]): M[A] = { def go(ft: FreeT[S, M, A]): M[Either[FreeT[S, M, A], A]] = ft match { case Suspend(ma) => M.flatMap(ma) { @@ -170,6 +175,15 @@ object FreeT extends FreeTInstances { def roll[S[_], M[_], A](value: S[FreeT[S, M, A]])(implicit M: Applicative[M]): FreeT[S, M, A] = liftF[S, M, FreeT[S, M, A]](value).flatMap(identity) + def compile[S[_], T[_], M[_]: Functor](st: FunctionK[S, T]): FunctionK[FreeT[S, M, ?], FreeT[T, M, ?]] = + new FunctionK[FreeT[S, M, ?], FreeT[T, M, ?]] { + def apply[A](f: FreeT[S, M, A]) = f.compile(st) + } + + def foldMap[S[_], M[_]: Monad](fk: FunctionK[S, M]): FunctionK[FreeT[S, M, ?], M] = + new FunctionK[FreeT[S, M, ?], M] { + def apply[A](f: FreeT[S, M, A]) = f.foldMap(fk) + } } private[free] sealed trait FreeTInstances3 { diff --git a/free/src/test/scala/cats/free/FreeTTests.scala b/free/src/test/scala/cats/free/FreeTTests.scala index a2ea3d5628..95b8e127b3 100644 --- a/free/src/test/scala/cats/free/FreeTTests.scala +++ b/free/src/test/scala/cats/free/FreeTTests.scala @@ -101,25 +101,29 @@ class FreeTTests extends CatsSuite { val d: FreeT[JustFunctor, JustFunctor, Int] = transLiftInstance.liftT[JustFunctor, Int](JustFunctor(1)) } - test("interpret to universal id equivalent to original instance") { + test("compile to universal id equivalent to original instance") { forAll { a: FreeTOption[Int] => - val b = a.interpret(FunctionK.id) + val b = a.compile(FunctionK.id) Eq[FreeTOption[Int]].eqv(a, b) should ===(true) + val fk = FreeT.compile[Option, Option, Option](FunctionK.id) + a should === (fk(a)) } } - test("interpret stack-safety") { + test("compile stack-safety") { val a = (0 until 50000).foldLeft(Applicative[FreeTOption].pure(()))( (fu, i) => fu.flatMap(u => Applicative[FreeTOption].pure(u)) ) - val b = a.interpret(FunctionK.id) // used to overflow + val b = a.compile(FunctionK.id) // used to overflow } test("foldMap consistent with runM") { forAll { a: FreeTOption[Int] => val x = a.runM(identity) val y = a.foldMap(FunctionK.id) + val fk = FreeT.foldMap[Option, Option](FunctionK.id) Eq[Option[Int]].eqv(x, y) should ===(true) + y should === (fk(a)) } } diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index de77c5ed1d..0f12febf75 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -26,6 +26,8 @@ class FreeTests extends CatsSuite { test("compile id"){ forAll { x: Free[List, Int] => x.compile(FunctionK.id[List]) should === (x) + val fk = Free.compile(FunctionK.id[List]) + fk(x) === x } } @@ -46,6 +48,9 @@ class FreeTests extends CatsSuite { val mapped = x.compile(headOptionU) val folded = mapped.foldMap(FunctionK.id[Option]) folded should === (x.foldMap(headOptionU)) + + val fk = Free.foldMap(headOptionU) + folded should === (fk(x)) } }