Skip to content

Commit

Permalink
Merge pull request #1261 from travisbrown/topic/future-monadrec
Browse files Browse the repository at this point in the history
Add MonadRec for Future and reinstate Future tests for JVM
  • Loading branch information
kailuowang authored Aug 3, 2016
2 parents a5b156f + cad6429 commit 0791198
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 5 deletions.
13 changes: 10 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ lazy val catsJVM = project.in(file(".catsJVM"))
.settings(moduleName := "cats")
.settings(catsSettings)
.settings(commonJvmSettings)
.aggregate(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testsJVM, docs, bench)
.dependsOn(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testsJVM % "test-internal -> test", bench % "compile-internal;test-internal -> test")
.aggregate(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testsJVM, jvm, docs, bench)
.dependsOn(macrosJVM, kernelJVM, kernelLawsJVM, coreJVM, lawsJVM, freeJVM, testsJVM % "test-internal -> test", jvm, bench % "compile-internal;test-internal -> test")

lazy val catsJS = project.in(file(".catsJS"))
.settings(moduleName := "cats")
Expand Down Expand Up @@ -290,6 +290,13 @@ lazy val js = project
.settings(commonJsSettings:_*)
.enablePlugins(ScalaJSPlugin)

// cats-jvm is JVM-only
lazy val jvm = project
.dependsOn(macrosJVM, coreJVM, testsJVM % "test-internal -> test")
.settings(moduleName := "cats-jvm")
.settings(catsSettings:_*)
.settings(commonJvmSettings:_*)

lazy val publishSettings = Seq(
homepage := Some(url("https://github.com/typelevel/cats")),
licenses := Seq("MIT" -> url("http://opensource.org/licenses/MIT")),
Expand Down Expand Up @@ -358,7 +365,7 @@ lazy val publishSettings = Seq(
) ++ credentialSettings ++ sharedPublishSettings ++ sharedReleaseProcess

// These aliases serialise the build for the benefit of Travis-CI.
addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;kernelLawsJVM/compile;lawsJVM/compile;freeJVM/compile;kernelLawsJVM/test;coreJVM/test;testsJVM/test;freeJVM/test;bench/test")
addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;kernelLawsJVM/compile;lawsJVM/compile;freeJVM/compile;kernelLawsJVM/test;coreJVM/test;testsJVM/test;freeJVM/test;jvm/test;bench/test")

addCommandAlias("validateJVM", ";scalastyle;buildJVM;makeSite")

Expand Down
14 changes: 12 additions & 2 deletions core/src/main/scala/cats/instances/future.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ import scala.concurrent.{ExecutionContext, Future}

trait FutureInstances extends FutureInstances1 {

implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] =
new FutureCoflatMap with MonadError[Future, Throwable]{
implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with MonadRec[Future] =
new FutureCoflatMap with MonadError[Future, Throwable] with MonadRec[Future] {
def pure[A](x: A): Future[A] = Future.successful(x)

def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f)

/**
* Note that while this implementation will not compile with `@tailrec`,
* it is in fact stack-safe.
*/
final def tailRecM[B, C](b: B)(f: B => Future[(B Xor C)]): Future[C] =
f(b).flatMap {
case Xor.Left(b1) => tailRecM(b1)(f)
case Xor.Right(c) => Future.successful(c)
}

def handleErrorWith[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) }

def raiseError[A](e: Throwable): Future[A] = Future.failed(e)
Expand Down
1 change: 1 addition & 0 deletions js/src/test/scala/cats/tests/FutureTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ class FutureTests extends CatsSuite {

checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int])
checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int])
checkAll("Future", MonadRecTests[Future].monadRec[Int, Int, Int])
}
39 changes: 39 additions & 0 deletions jvm/src/test/scala/cats/tests/FutureTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cats
package jvm
package tests

import cats.data.Xor
import cats.laws.discipline._
import cats.tests.CatsSuite

import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

import org.scalacheck.Arbitrary
import org.scalacheck.Arbitrary.arbitrary

class FutureTests extends CatsSuite {
val timeout = 3.seconds

def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] =
f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) }

implicit def eqfa[A: Eq]: Eq[Future[A]] =
new Eq[Future[A]] {
def eqv(fx: Future[A], fy: Future[A]): Boolean = {
val fz = futureXor(fx) zip futureXor(fy)
Await.result(fz.map { case (tx, ty) => tx === ty }, timeout)
}
}

implicit val throwableEq: Eq[Throwable] =
Eq.fromUniversalEquals

// Need non-fatal Throwables for Future recoverWith/handleError
implicit val nonFatalArbitrary: Arbitrary[Throwable] =
Arbitrary(arbitrary[Exception].map(identity))

checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int])
checkAll("Future", MonadRecTests[Future].monadRec[Int, Int, Int])
}

0 comments on commit 0791198

Please sign in to comment.