Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add foldMap/compile FunctionK to Free companion #1411

Merged
merged 2 commits into from
Oct 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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