Skip to content

Commit

Permalink
Add foldMap/compile FunctionK to Free companion (#1411)
Browse files Browse the repository at this point in the history
* Add foldMap/compile FunctionK to Free companion

* Rename interpret and replace ~> (#1433)

* Use FunctionK instead of ~> alias in FreeT

* Rename interpret in FreeT to compile for consistency with Free
  • Loading branch information
johnynek authored Oct 26, 2016
1 parent d0143ee commit 08b72eb
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 8 deletions.
15 changes: 15 additions & 0 deletions free/src/main/scala/cats/free/Free.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 18 additions & 4 deletions free/src/main/scala/cats/free/FreeT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -27,19 +29,22 @@ 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)))
case Suspend(m) =>
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))))
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 8 additions & 4 deletions free/src/test/scala/cats/free/FreeTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down
5 changes: 5 additions & 0 deletions free/src/test/scala/cats/free/FreeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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))
}
}

Expand Down

0 comments on commit 08b72eb

Please sign in to comment.