From d91c0959039c9ce35c7af3d2fa28b658b310eda4 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Thu, 9 Mar 2017 18:45:07 +0200 Subject: [PATCH 1/6] Fixes #1316: add ApplicativeEval and MonadDefer typeclasses --- .../src/main/scala/cats/ApplicativeEval.scala | 25 ++++++ core/src/main/scala/cats/Eval.scala | 6 +- core/src/main/scala/cats/MonadDefer.scala | 21 +++++ .../main/scala/cats/instances/future.scala | 5 +- core/src/main/scala/cats/instances/try.scala | 5 +- .../test/scala/cats/tests/FutureTests.scala | 1 + .../test/scala/cats/tests/FutureTests.scala | 1 + .../scala/cats/laws/ApplicativeEvalLaws.scala | 24 ++++++ .../main/scala/cats/laws/MonadDeferLaws.scala | 62 +++++++++++++++ .../discipline/ApplicativeEvalTests.scala | 70 +++++++++++++++++ .../laws/discipline/MonadDeferTests.scala | 77 +++++++++++++++++++ .../src/test/scala/cats/tests/EvalTests.scala | 6 +- .../src/test/scala/cats/tests/TryTests.scala | 4 +- 13 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 core/src/main/scala/cats/ApplicativeEval.scala create mode 100644 core/src/main/scala/cats/MonadDefer.scala create mode 100644 laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala create mode 100644 laws/src/main/scala/cats/laws/MonadDeferLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala diff --git a/core/src/main/scala/cats/ApplicativeEval.scala b/core/src/main/scala/cats/ApplicativeEval.scala new file mode 100644 index 0000000000..7632c0b74c --- /dev/null +++ b/core/src/main/scala/cats/ApplicativeEval.scala @@ -0,0 +1,25 @@ +package cats + +import simulacrum.typeclass +import cats.Eval.always + +/** + * A type class that allows lifting any value into the applicative + * context, with its evaluation being controlled by [[Eval]] and + * supporting optional laziness. + */ +@typeclass trait ApplicativeEval[F[_]] extends Applicative[F] { + /** + * Lifts any value into the `F[_]` applicative context, where the + * evaluation is controlled by [[Eval]] and can be optionally lazy. + */ + def eval[A](a: Eval[A]): F[A] + + /** + * Lifts the given by-name value in the `F[_]` context, with + * optional laziness. + * + * Alias for `eval(always(a))`. + */ + final def delay[A](a: => A): F[A] = eval(always(a)) +} diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 069243597a..ac5b67b723 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -325,10 +325,12 @@ object Eval extends EvalInstances { private[cats] trait EvalInstances extends EvalInstances0 { - implicit val catsBimonadForEval: Bimonad[Eval] with Monad[Eval] = - new Bimonad[Eval] with Monad[Eval] { + implicit val catsBimonadForEval: Bimonad[Eval] with MonadDefer[Eval] = + new Bimonad[Eval] with MonadDefer[Eval] { override def map[A, B](fa: Eval[A])(f: A => B): Eval[B] = fa.map(f) def pure[A](a: A): Eval[A] = Now(a) + def eval[A](a: Eval[A]): Eval[A] = a + override def defer[A](fa: => Eval[A]): Eval[A] = Eval.defer(fa) def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = fa.flatMap(f) def extract[A](la: Eval[A]): A = la.value def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Later(f(fa)) diff --git a/core/src/main/scala/cats/MonadDefer.scala b/core/src/main/scala/cats/MonadDefer.scala new file mode 100644 index 0000000000..e255b111a7 --- /dev/null +++ b/core/src/main/scala/cats/MonadDefer.scala @@ -0,0 +1,21 @@ +package cats + +import simulacrum.typeclass +import cats.Eval.always + +/** + * A [[Monad monad]] that allows for arbitrarily delaying the + * evaluation of an operation, triggering its execution on each run. + * + * @see [[ApplicativeEval]] for capturing effects in an `F[_]` + * applicative context, but without the repeating side-effects + * requirement. + */ +@typeclass trait MonadDefer[F[_]] extends Monad[F] with ApplicativeEval[F] { + /** + * Returns an `F[A]` that evaluates the provided by-name `fa` + * parameter on each run. In essence it builds an `F[A]` factory. + */ + def defer[A](fa: => F[A]): F[A] = + flatten(eval(always(fa))) +} diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index 79f270d22f..d22de4bb22 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -6,9 +6,10 @@ import scala.concurrent.{ExecutionContext, Future} trait FutureInstances extends FutureInstances1 { - implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] = - new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] { + implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] with ApplicativeEval[Future] = + new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with ApplicativeEval[Future] { def pure[A](x: A): Future[A] = Future.successful(x) + def eval[A](a: Eval[A]): Future[A] = Future(a.value) def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index fe134b9302..2e28db92ed 100644 --- a/core/src/main/scala/cats/instances/try.scala +++ b/core/src/main/scala/cats/instances/try.scala @@ -10,9 +10,10 @@ import scala.annotation.tailrec trait TryInstances extends TryInstances1 { // scalastyle:off method.length - implicit def catsStdInstancesForTry: MonadError[Try, Throwable] with CoflatMap[Try] with Traverse[Try] with Monad[Try] = - new TryCoflatMap with MonadError[Try, Throwable] with Traverse[Try] with Monad[Try] { + implicit def catsStdInstancesForTry: MonadError[Try, Throwable] with CoflatMap[Try] with Traverse[Try] with Monad[Try] with ApplicativeEval[Try] = + new TryCoflatMap with MonadError[Try, Throwable] with Traverse[Try] with Monad[Try] with ApplicativeEval[Try] { def pure[A](x: A): Try[A] = Success(x) + def eval[A](a: Eval[A]): Try[A] = Try(a.value) override def product[A, B](ta: Try[A], tb: Try[B]): Try[(A, B)] = (ta, tb) match { case (Success(a), Success(b)) => Success((a, b)) diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index e248189d34..e8ed98d307 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -54,4 +54,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", MonadTests[Future].monad[Int, Int, Int]) + checkAll("Future", ApplicativeEvalTests[Future].applicativeEvalWithError[Int, Int, Int]) } diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 669d065500..b68d9e9f79 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -36,4 +36,5 @@ class FutureTests extends CatsSuite { checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) + checkAll("Future", ApplicativeEvalTests[Future].applicativeEvalWithError[Int, Int, Int]) } diff --git a/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala new file mode 100644 index 0000000000..f20c9d8ae6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala @@ -0,0 +1,24 @@ +package cats +package laws + +import cats.Eval.{always, now} +import cats.syntax.all._ + +trait ApplicativeEvalLaws[F[_]] extends ApplicativeLaws[F] { + implicit override def F: ApplicativeEval[F] + + def evalEquivalenceWithPure[A](a: A): IsEq[F[A]] = + F.eval(now(a)) <-> F.pure(a) + + def evalConsistentWithPureMapped[A,B](a: A, f: A => B): IsEq[F[B]] = + F.eval(always(f(a))) <-> F.pure(a).map(f) + + def evalCapturesExceptions[A](ex: Throwable) + (implicit A: ApplicativeError[F,Throwable]): IsEq[F[A]] = + F.eval[A](always(throw ex)) <-> A.raiseError[A](ex) +} + +object ApplicativeEvalLaws { + def apply[F[_]](implicit ev: ApplicativeEval[F]): ApplicativeEvalLaws[F] = + new ApplicativeEvalLaws[F] { def F: ApplicativeEval[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/MonadDeferLaws.scala b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala new file mode 100644 index 0000000000..0ed8d05cac --- /dev/null +++ b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala @@ -0,0 +1,62 @@ +package cats +package laws + +import cats.syntax.all._ +import cats.laws.MonadDeferLaws.StatefulBox + +trait MonadDeferLaws[F[_]] extends ApplicativeEvalLaws[F] with MonadLaws[F] { + implicit override def F: MonadDefer[F] + + def evalSequenceConsistentWithPureMap[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { + def tr(s: StatefulBox[A]) = s.transform(a => f(a, b)) + val state1 = new StatefulBox(a) + val state2 = new StatefulBox(a) + + val lh = F.delay(tr(state1)) + val rh = F.pure(state2).map(tr) + + lh.flatMap(_ => lh) <-> rh.flatMap(_ => rh) + } + + def deferSequenceConsistentWithPureFlatMap[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { + def tr(s: StatefulBox[A]) = F.pure(s.transform(a => f(a, b))) + val state1 = new StatefulBox(a) + val state2 = new StatefulBox(a) + + val lh = F.defer(tr(state1)) + val rh = F.pure(state2).flatMap(tr) + + lh.flatMap(_ => lh) <-> rh.flatMap(_ => rh) + } + + def evalRepeatsSideEffects[A,B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { + val state = new StatefulBox(a) + val fa = F.delay(state.transform(a => f(a, b))) + fa.flatMap(_ => fa) <-> F.pure(f(f(a,b), b)) + } + + def deferRepeatsSideEffects[A,B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { + val state = new StatefulBox(a) + val fa = F.defer(F.pure(state.transform(a => f(a,b)))) + fa.flatMap(_ => fa) <-> F.pure(f(f(a,b), b)) + } +} + +object MonadDeferLaws { + def apply[F[_]](implicit ev: MonadDefer[F]): MonadDeferLaws[F] = + new MonadDeferLaws[F] { def F: MonadDefer[F] = ev } + + /** + * A boxed and synchronized variable to use for + * testing deferred side effects. + */ + final class StatefulBox[A](initial: A) { + private[this] var state = initial + + def get: A = + synchronized(state) + + def transform(f: A => A): A = + synchronized{ state = f(state); state } + } +} \ No newline at end of file diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala new file mode 100644 index 0000000000..f0e45dddde --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala @@ -0,0 +1,70 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.CartesianTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen} +import org.scalacheck.Prop.forAll + +trait ApplicativeEvalTests[F[_]] extends ApplicativeTests[F] { + def laws: ApplicativeEvalLaws[F] + + def applicativeEval[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] + ): RuleSet = { + new DefaultRuleSet( + name = "applicativeEval", + parent = Some(applicative[A, B, C]), + "eval consistent with pure" -> forAll(laws.evalEquivalenceWithPure[A] _), + "eval consistent with pure mapped" -> forAll(laws.evalConsistentWithPureMapped[A,B] _) + ) + } + + /** + * In addition to the tests specified by [[applicativeEval]], it adds + * an extra `ApplicativeError[F,Throwable]` restriction, which + * enables extra laws. + */ + def applicativeEvalWithError[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F], + apErr: ApplicativeError[F, Throwable] + ): RuleSet = { + new DefaultRuleSet( + name = "applicativeEvalWithError", + parent = Some(applicativeEval[A, B, C]), + "eval captures exceptions" -> forAll(laws.evalCapturesExceptions[A] _) + ) + } +} + +object ApplicativeEvalTests { + def apply[F[_]: ApplicativeEval]: ApplicativeEvalTests[F] = + new ApplicativeEvalTests[F] { + def laws: ApplicativeEvalLaws[F] = ApplicativeEvalLaws[F] + } +} + diff --git a/laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala new file mode 100644 index 0000000000..b575009ad5 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala @@ -0,0 +1,77 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.CartesianTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} +import org.scalacheck.Prop.forAll + +trait MonadDeferTests[F[_]] extends ApplicativeEvalTests[F] with MonadTests[F] { + def laws: MonadDeferLaws[F] + + def monadDefer[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + EqFInt: Eq[F[Int]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + def name: String = "monadDefer" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monad[A, B, C], applicativeEval[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "eval sequence consistent with pure.map" -> forAll(laws.evalSequenceConsistentWithPureMap[A, B] _), + "eval repeats side effects" -> forAll(laws.evalRepeatsSideEffects[A, B] _), + "defer sequence consistent with pure.flatMap" -> forAll(laws.deferSequenceConsistentWithPureFlatMap[A, B] _), + "defer repeats side effects" -> forAll(laws.deferRepeatsSideEffects[A, B] _) + ) + } + } + + /** + * In addition to the tests specified by [[monadDefer]], it adds + * an extra `ApplicativeError[F,Throwable]` restriction, which + * enables extra laws. + */ + def monadDeferWithError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + EqFInt: Eq[F[Int]], + iso: Isomorphisms[F], + apErr: ApplicativeError[F, Throwable] + ): RuleSet = { + new RuleSet { + def name: String = "monadDeferWithError" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monadDefer[A, B, C], applicativeEvalWithError[A, B, C]) + def props: Seq[(String, Prop)] = Seq.empty + } + } +} + +object MonadDeferTests { + def apply[F[_]: MonadDefer]: MonadDeferTests[F] = + new MonadDeferTests[F] { + def laws: MonadDeferLaws[F] = MonadDeferLaws[F] + } +} diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index 2c821343b0..3a849c2646 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -3,7 +3,7 @@ package tests import scala.math.min import cats.laws.ComonadLaws -import cats.laws.discipline.{BimonadTests, CartesianTests, MonadTests, ReducibleTests, SerializableTests} +import cats.laws.discipline.{BimonadTests, CartesianTests, MonadTests, MonadDeferTests, ReducibleTests, SerializableTests} import cats.laws.discipline.arbitrary._ import cats.kernel.laws.{GroupLaws, OrderLaws} @@ -83,10 +83,12 @@ class EvalTests extends CatsSuite { { implicit val iso = CartesianTests.Isomorphisms.invariant[Eval] checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int]) + checkAll("Eval[Int]", MonadDeferTests[Eval].monadDefer[Int, Int, Int]) checkAll("Eval[Int]", MonadTests[Eval].monad[Int, Int, Int]) } + checkAll("Bimonad[Eval]", SerializableTests.serializable(Bimonad[Eval])) - checkAll("Monad[Eval]", SerializableTests.serializable(Monad[Eval])) + checkAll("MonadDefer[Eval]", SerializableTests.serializable(MonadDefer[Eval])) checkAll("Eval[Int]", ReducibleTests[Eval].reducible[Option, Int, Int]) checkAll("Reducible[Eval]", SerializableTests.serializable(Reducible[Eval])) diff --git a/tests/src/test/scala/cats/tests/TryTests.scala b/tests/src/test/scala/cats/tests/TryTests.scala index 3548372aa4..f24f0ef95a 100644 --- a/tests/src/test/scala/cats/tests/TryTests.scala +++ b/tests/src/test/scala/cats/tests/TryTests.scala @@ -4,7 +4,6 @@ package tests import cats.laws.{ApplicativeLaws, CoflatMapLaws, FlatMapLaws, MonadLaws} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ - import scala.util.{Success, Try} class TryTests extends CatsSuite { @@ -25,6 +24,9 @@ class TryTests extends CatsSuite { checkAll("Try", MonadTests[Try].monad[Int, Int, Int]) checkAll("Monad[Try]", SerializableTests.serializable(Monad[Try])) + checkAll("Try[Int]", ApplicativeEvalTests[Try].applicativeEvalWithError[Int, Int, Int]) + checkAll("ApplicativeEval[Try]", SerializableTests.serializable(ApplicativeEval[Try])) + test("show") { forAll { fs: Try[String] => fs.show should === (fs.toString) From 6ceece15235770132c54f042f69509091cad92ac Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Thu, 9 Mar 2017 19:24:38 +0200 Subject: [PATCH 2/6] Fix ScalaStyle issues --- .../main/scala/cats/laws/ApplicativeEvalLaws.scala | 4 ++-- laws/src/main/scala/cats/laws/MonadDeferLaws.scala | 12 ++++++------ .../cats/laws/discipline/ApplicativeEvalTests.scala | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala index f20c9d8ae6..500aef3e85 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala @@ -10,11 +10,11 @@ trait ApplicativeEvalLaws[F[_]] extends ApplicativeLaws[F] { def evalEquivalenceWithPure[A](a: A): IsEq[F[A]] = F.eval(now(a)) <-> F.pure(a) - def evalConsistentWithPureMapped[A,B](a: A, f: A => B): IsEq[F[B]] = + def evalConsistentWithPureMapped[A, B](a: A, f: A => B): IsEq[F[B]] = F.eval(always(f(a))) <-> F.pure(a).map(f) def evalCapturesExceptions[A](ex: Throwable) - (implicit A: ApplicativeError[F,Throwable]): IsEq[F[A]] = + (implicit A: ApplicativeError[F, Throwable]): IsEq[F[A]] = F.eval[A](always(throw ex)) <-> A.raiseError[A](ex) } diff --git a/laws/src/main/scala/cats/laws/MonadDeferLaws.scala b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala index 0ed8d05cac..daf73238ba 100644 --- a/laws/src/main/scala/cats/laws/MonadDeferLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala @@ -29,16 +29,16 @@ trait MonadDeferLaws[F[_]] extends ApplicativeEvalLaws[F] with MonadLaws[F] { lh.flatMap(_ => lh) <-> rh.flatMap(_ => rh) } - def evalRepeatsSideEffects[A,B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { + def evalRepeatsSideEffects[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { val state = new StatefulBox(a) val fa = F.delay(state.transform(a => f(a, b))) - fa.flatMap(_ => fa) <-> F.pure(f(f(a,b), b)) + fa.flatMap(_ => fa) <-> F.pure(f(f(a, b), b)) } - def deferRepeatsSideEffects[A,B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { + def deferRepeatsSideEffects[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { val state = new StatefulBox(a) - val fa = F.defer(F.pure(state.transform(a => f(a,b)))) - fa.flatMap(_ => fa) <-> F.pure(f(f(a,b), b)) + val fa = F.defer(F.pure(state.transform(a => f(a, b)))) + fa.flatMap(_ => fa) <-> F.pure(f(f(a, b), b)) } } @@ -59,4 +59,4 @@ object MonadDeferLaws { def transform(f: A => A): A = synchronized{ state = f(state); state } } -} \ No newline at end of file +} diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala index f0e45dddde..aa27051db2 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala @@ -28,7 +28,7 @@ trait ApplicativeEvalTests[F[_]] extends ApplicativeTests[F] { name = "applicativeEval", parent = Some(applicative[A, B, C]), "eval consistent with pure" -> forAll(laws.evalEquivalenceWithPure[A] _), - "eval consistent with pure mapped" -> forAll(laws.evalConsistentWithPureMapped[A,B] _) + "eval consistent with pure mapped" -> forAll(laws.evalConsistentWithPureMapped[A, B] _) ) } From b5d4d43b8d021c1038762bed1da48f26722095d0 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Sat, 25 Mar 2017 10:44:49 -0400 Subject: [PATCH 3/6] Dropped AppplicativeEval --- .../src/main/scala/cats/ApplicativeEval.scala | 25 ------- core/src/main/scala/cats/Eval.scala | 2 +- core/src/main/scala/cats/MonadDefer.scala | 21 ++++-- .../main/scala/cats/instances/future.scala | 4 +- core/src/main/scala/cats/instances/try.scala | 4 +- .../test/scala/cats/tests/FutureTests.scala | 1 - .../scala/cats/laws/ApplicativeEvalLaws.scala | 24 ------- .../main/scala/cats/laws/MonadDeferLaws.scala | 54 ++++++++------ .../discipline/ApplicativeEvalTests.scala | 70 ------------------- .../laws/discipline/MonadDeferTests.scala | 21 +++--- .../src/test/scala/cats/tests/TryTests.scala | 3 - 11 files changed, 63 insertions(+), 166 deletions(-) delete mode 100644 core/src/main/scala/cats/ApplicativeEval.scala delete mode 100644 laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala delete mode 100644 laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala diff --git a/core/src/main/scala/cats/ApplicativeEval.scala b/core/src/main/scala/cats/ApplicativeEval.scala deleted file mode 100644 index 7632c0b74c..0000000000 --- a/core/src/main/scala/cats/ApplicativeEval.scala +++ /dev/null @@ -1,25 +0,0 @@ -package cats - -import simulacrum.typeclass -import cats.Eval.always - -/** - * A type class that allows lifting any value into the applicative - * context, with its evaluation being controlled by [[Eval]] and - * supporting optional laziness. - */ -@typeclass trait ApplicativeEval[F[_]] extends Applicative[F] { - /** - * Lifts any value into the `F[_]` applicative context, where the - * evaluation is controlled by [[Eval]] and can be optionally lazy. - */ - def eval[A](a: Eval[A]): F[A] - - /** - * Lifts the given by-name value in the `F[_]` context, with - * optional laziness. - * - * Alias for `eval(always(a))`. - */ - final def delay[A](a: => A): F[A] = eval(always(a)) -} diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index ac5b67b723..25879db133 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -329,7 +329,7 @@ private[cats] trait EvalInstances extends EvalInstances0 { new Bimonad[Eval] with MonadDefer[Eval] { override def map[A, B](fa: Eval[A])(f: A => B): Eval[B] = fa.map(f) def pure[A](a: A): Eval[A] = Now(a) - def eval[A](a: Eval[A]): Eval[A] = a + override def delay[A](a: => A): Eval[A] = Always(a) override def defer[A](fa: => Eval[A]): Eval[A] = Eval.defer(fa) def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = fa.flatMap(f) def extract[A](la: Eval[A]): A = la.value diff --git a/core/src/main/scala/cats/MonadDefer.scala b/core/src/main/scala/cats/MonadDefer.scala index e255b111a7..0b89c29a9b 100644 --- a/core/src/main/scala/cats/MonadDefer.scala +++ b/core/src/main/scala/cats/MonadDefer.scala @@ -1,21 +1,28 @@ package cats import simulacrum.typeclass -import cats.Eval.always /** * A [[Monad monad]] that allows for arbitrarily delaying the * evaluation of an operation, triggering its execution on each run. * - * @see [[ApplicativeEval]] for capturing effects in an `F[_]` - * applicative context, but without the repeating side-effects - * requirement. + * Instances of this type-class have the following properties: + * + * - suspend any side-effects for later, until evaluated + * - suspension has `always` semantics, meaning that on each + * evaluation of `F[_]` the evaluation, along with any + * side-effects, get repeated + * - the `flatMap` operation is stack safe and can be + * used in recursive loops */ -@typeclass trait MonadDefer[F[_]] extends Monad[F] with ApplicativeEval[F] { +@typeclass trait MonadDefer[F[_]] extends Monad[F] { /** * Returns an `F[A]` that evaluates the provided by-name `fa` * parameter on each run. In essence it builds an `F[A]` factory. */ - def defer[A](fa: => F[A]): F[A] = - flatten(eval(always(fa))) + def defer[A](fa: => F[A]): F[A] + + /** Lifts the given by-name value in the `F[_]` context. */ + def delay[A](a: => A): F[A] = + defer(pure(a)) } diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index d22de4bb22..ba40aec2fd 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -6,8 +6,8 @@ import scala.concurrent.{ExecutionContext, Future} trait FutureInstances extends FutureInstances1 { - implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] with ApplicativeEval[Future] = - new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with ApplicativeEval[Future] { + implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] = + new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] { def pure[A](x: A): Future[A] = Future.successful(x) def eval[A](a: Eval[A]): Future[A] = Future(a.value) diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index 2e28db92ed..9a3ed6d4d4 100644 --- a/core/src/main/scala/cats/instances/try.scala +++ b/core/src/main/scala/cats/instances/try.scala @@ -10,8 +10,8 @@ import scala.annotation.tailrec trait TryInstances extends TryInstances1 { // scalastyle:off method.length - implicit def catsStdInstancesForTry: MonadError[Try, Throwable] with CoflatMap[Try] with Traverse[Try] with Monad[Try] with ApplicativeEval[Try] = - new TryCoflatMap with MonadError[Try, Throwable] with Traverse[Try] with Monad[Try] with ApplicativeEval[Try] { + implicit def catsStdInstancesForTry: MonadError[Try, Throwable] with CoflatMap[Try] with Traverse[Try] with Monad[Try] = + new TryCoflatMap with MonadError[Try, Throwable] with Traverse[Try] with Monad[Try] { def pure[A](x: A): Try[A] = Success(x) def eval[A](a: Eval[A]): Try[A] = Try(a.value) diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index e8ed98d307..e248189d34 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -54,5 +54,4 @@ 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", MonadTests[Future].monad[Int, Int, Int]) - checkAll("Future", ApplicativeEvalTests[Future].applicativeEvalWithError[Int, Int, Int]) } diff --git a/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala deleted file mode 100644 index 500aef3e85..0000000000 --- a/laws/src/main/scala/cats/laws/ApplicativeEvalLaws.scala +++ /dev/null @@ -1,24 +0,0 @@ -package cats -package laws - -import cats.Eval.{always, now} -import cats.syntax.all._ - -trait ApplicativeEvalLaws[F[_]] extends ApplicativeLaws[F] { - implicit override def F: ApplicativeEval[F] - - def evalEquivalenceWithPure[A](a: A): IsEq[F[A]] = - F.eval(now(a)) <-> F.pure(a) - - def evalConsistentWithPureMapped[A, B](a: A, f: A => B): IsEq[F[B]] = - F.eval(always(f(a))) <-> F.pure(a).map(f) - - def evalCapturesExceptions[A](ex: Throwable) - (implicit A: ApplicativeError[F, Throwable]): IsEq[F[A]] = - F.eval[A](always(throw ex)) <-> A.raiseError[A](ex) -} - -object ApplicativeEvalLaws { - def apply[F[_]](implicit ev: ApplicativeEval[F]): ApplicativeEvalLaws[F] = - new ApplicativeEvalLaws[F] { def F: ApplicativeEval[F] = ev } -} diff --git a/laws/src/main/scala/cats/laws/MonadDeferLaws.scala b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala index daf73238ba..3543cb99ea 100644 --- a/laws/src/main/scala/cats/laws/MonadDeferLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala @@ -4,32 +4,16 @@ package laws import cats.syntax.all._ import cats.laws.MonadDeferLaws.StatefulBox -trait MonadDeferLaws[F[_]] extends ApplicativeEvalLaws[F] with MonadLaws[F] { +trait MonadDeferLaws[F[_]] extends MonadLaws[F] { implicit override def F: MonadDefer[F] - def evalSequenceConsistentWithPureMap[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { - def tr(s: StatefulBox[A]) = s.transform(a => f(a, b)) - val state1 = new StatefulBox(a) - val state2 = new StatefulBox(a) + def delayEquivalenceWithPure[A](a: A): IsEq[F[A]] = + F.delay(a) <-> F.pure(a) - val lh = F.delay(tr(state1)) - val rh = F.pure(state2).map(tr) + def delayEquivalenceWithDefer[A, B](a: A, f: A => B): IsEq[F[B]] = + F.delay(f(a)) <-> F.defer(F.pure(f(a))) - lh.flatMap(_ => lh) <-> rh.flatMap(_ => rh) - } - - def deferSequenceConsistentWithPureFlatMap[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { - def tr(s: StatefulBox[A]) = F.pure(s.transform(a => f(a, b))) - val state1 = new StatefulBox(a) - val state2 = new StatefulBox(a) - - val lh = F.defer(tr(state1)) - val rh = F.pure(state2).flatMap(tr) - - lh.flatMap(_ => lh) <-> rh.flatMap(_ => rh) - } - - def evalRepeatsSideEffects[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { + def delayRepeatsSideEffects[A, B](a: A, b: B, f: (A, B) => A): IsEq[F[A]] = { val state = new StatefulBox(a) val fa = F.delay(state.transform(a => f(a, b))) fa.flatMap(_ => fa) <-> F.pure(f(f(a, b), b)) @@ -40,6 +24,32 @@ trait MonadDeferLaws[F[_]] extends ApplicativeEvalLaws[F] with MonadLaws[F] { val fa = F.defer(F.pure(state.transform(a => f(a, b)))) fa.flatMap(_ => fa) <-> F.pure(f(f(a, b), b)) } + + + lazy val flatMapStackSafety: IsEq[F[Int]] = { + // tailRecM expressed with flatMap + def loop[A, B](a: A)(f: A => F[Either[A, B]]): F[B] = + F.flatMap(f(a)) { + case Right(b) => + F.pure(b) + case Left(nextA) => + loop(nextA)(f) + } + + val n = 50000 + val res = loop(0)(i => F.pure(if (i < n) Either.left(i + 1) else Either.right(i))) + res <-> F.pure(n) + } + + /** Optional law for `ApplicativeError`. */ + def delayCapturesExceptions[A](ex: Throwable) + (implicit A: ApplicativeError[F, Throwable]): IsEq[F[A]] = + F.delay[A](throw ex) <-> A.raiseError[A](ex) + + /** Optional law for `ApplicativeError`. */ + def deferCapturesExceptions[A](ex: Throwable) + (implicit A: ApplicativeError[F, Throwable]): IsEq[F[A]] = + F.defer[A](throw ex) <-> A.raiseError[A](ex) } object MonadDeferLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala deleted file mode 100644 index aa27051db2..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeEvalTests.scala +++ /dev/null @@ -1,70 +0,0 @@ -package cats -package laws -package discipline - -import cats.laws.discipline.CartesianTests.Isomorphisms -import org.scalacheck.{Arbitrary, Cogen} -import org.scalacheck.Prop.forAll - -trait ApplicativeEvalTests[F[_]] extends ApplicativeTests[F] { - def laws: ApplicativeEvalLaws[F] - - def applicativeEval[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbFA: Arbitrary[F[A]], - ArbFB: Arbitrary[F[B]], - ArbFC: Arbitrary[F[C]], - ArbFAtoB: Arbitrary[F[A => B]], - ArbFBtoC: Arbitrary[F[B => C]], - CogenA: Cogen[A], - CogenB: Cogen[B], - CogenC: Cogen[C], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]], - EqFABC: Eq[F[(A, B, C)]], - iso: Isomorphisms[F] - ): RuleSet = { - new DefaultRuleSet( - name = "applicativeEval", - parent = Some(applicative[A, B, C]), - "eval consistent with pure" -> forAll(laws.evalEquivalenceWithPure[A] _), - "eval consistent with pure mapped" -> forAll(laws.evalConsistentWithPureMapped[A, B] _) - ) - } - - /** - * In addition to the tests specified by [[applicativeEval]], it adds - * an extra `ApplicativeError[F,Throwable]` restriction, which - * enables extra laws. - */ - def applicativeEvalWithError[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbFA: Arbitrary[F[A]], - ArbFB: Arbitrary[F[B]], - ArbFC: Arbitrary[F[C]], - ArbFAtoB: Arbitrary[F[A => B]], - ArbFBtoC: Arbitrary[F[B => C]], - CogenA: Cogen[A], - CogenB: Cogen[B], - CogenC: Cogen[C], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]], - EqFABC: Eq[F[(A, B, C)]], - iso: Isomorphisms[F], - apErr: ApplicativeError[F, Throwable] - ): RuleSet = { - new DefaultRuleSet( - name = "applicativeEvalWithError", - parent = Some(applicativeEval[A, B, C]), - "eval captures exceptions" -> forAll(laws.evalCapturesExceptions[A] _) - ) - } -} - -object ApplicativeEvalTests { - def apply[F[_]: ApplicativeEval]: ApplicativeEvalTests[F] = - new ApplicativeEvalTests[F] { - def laws: ApplicativeEvalLaws[F] = ApplicativeEvalLaws[F] - } -} - diff --git a/laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala index b575009ad5..e54efa67df 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadDeferTests.scala @@ -2,11 +2,12 @@ package cats package laws package discipline +import catalysts.Platform import cats.laws.discipline.CartesianTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Prop.forAll -trait MonadDeferTests[F[_]] extends ApplicativeEvalTests[F] with MonadTests[F] { +trait MonadDeferTests[F[_]] extends MonadTests[F] { def laws: MonadDeferLaws[F] def monadDefer[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -28,18 +29,17 @@ trait MonadDeferTests[F[_]] extends ApplicativeEvalTests[F] with MonadTests[F] { new RuleSet { def name: String = "monadDefer" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(monad[A, B, C], applicativeEval[A, B, C]) + def parents: Seq[RuleSet] = Seq(monad[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "eval sequence consistent with pure.map" -> forAll(laws.evalSequenceConsistentWithPureMap[A, B] _), - "eval repeats side effects" -> forAll(laws.evalRepeatsSideEffects[A, B] _), - "defer sequence consistent with pure.flatMap" -> forAll(laws.deferSequenceConsistentWithPureFlatMap[A, B] _), + "delay equivalence with pure" -> forAll(laws.delayEquivalenceWithPure[A] _), + "delay repeats side effects" -> forAll(laws.delayRepeatsSideEffects[A, B] _), "defer repeats side effects" -> forAll(laws.deferRepeatsSideEffects[A, B] _) - ) + ) ++ (if (Platform.isJvm) Seq[(String, Prop)]("flatMap stack safety" -> Prop.lzy(laws.flatMapStackSafety)) else Seq.empty) } } /** - * In addition to the tests specified by [[monadDefer]], it adds + * In addition to the tests specified by [[MonadDefer]], it adds * an extra `ApplicativeError[F,Throwable]` restriction, which * enables extra laws. */ @@ -63,8 +63,11 @@ trait MonadDeferTests[F[_]] extends ApplicativeEvalTests[F] with MonadTests[F] { new RuleSet { def name: String = "monadDeferWithError" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(monadDefer[A, B, C], applicativeEvalWithError[A, B, C]) - def props: Seq[(String, Prop)] = Seq.empty + def parents: Seq[RuleSet] = Seq(monadDefer[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "delay captures exceptions" -> forAll(laws.delayCapturesExceptions[A] _), + "defer captures exceptions" -> forAll(laws.deferCapturesExceptions[A] _) + ) } } } diff --git a/tests/src/test/scala/cats/tests/TryTests.scala b/tests/src/test/scala/cats/tests/TryTests.scala index f24f0ef95a..e9892049c9 100644 --- a/tests/src/test/scala/cats/tests/TryTests.scala +++ b/tests/src/test/scala/cats/tests/TryTests.scala @@ -24,9 +24,6 @@ class TryTests extends CatsSuite { checkAll("Try", MonadTests[Try].monad[Int, Int, Int]) checkAll("Monad[Try]", SerializableTests.serializable(Monad[Try])) - checkAll("Try[Int]", ApplicativeEvalTests[Try].applicativeEvalWithError[Int, Int, Int]) - checkAll("ApplicativeEval[Try]", SerializableTests.serializable(ApplicativeEval[Try])) - test("show") { forAll { fs: Try[String] => fs.show should === (fs.toString) From 907506d298385c5930197109784922f971e54c19 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Sat, 25 Mar 2017 11:02:02 -0400 Subject: [PATCH 4/6] Remove junk --- core/src/main/scala/cats/instances/future.scala | 1 - core/src/main/scala/cats/instances/try.scala | 1 - 2 files changed, 2 deletions(-) diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index ba40aec2fd..79f270d22f 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -9,7 +9,6 @@ trait FutureInstances extends FutureInstances1 { implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] = new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] { def pure[A](x: A): Future[A] = Future.successful(x) - def eval[A](a: Eval[A]): Future[A] = Future(a.value) def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index 9a3ed6d4d4..fe134b9302 100644 --- a/core/src/main/scala/cats/instances/try.scala +++ b/core/src/main/scala/cats/instances/try.scala @@ -13,7 +13,6 @@ trait TryInstances extends TryInstances1 { implicit def catsStdInstancesForTry: MonadError[Try, Throwable] with CoflatMap[Try] with Traverse[Try] with Monad[Try] = new TryCoflatMap with MonadError[Try, Throwable] with Traverse[Try] with Monad[Try] { def pure[A](x: A): Try[A] = Success(x) - def eval[A](a: Eval[A]): Try[A] = Try(a.value) override def product[A, B](ta: Try[A], tb: Try[B]): Try[(A, B)] = (ta, tb) match { case (Success(a), Success(b)) => Success((a, b)) From a1ff44a633b9209ecc9a4277675c54aa345a0d5b Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Sat, 25 Mar 2017 11:03:03 -0400 Subject: [PATCH 5/6] Remove junk --- jvm/src/test/scala/cats/tests/FutureTests.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index b68d9e9f79..669d065500 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -36,5 +36,4 @@ class FutureTests extends CatsSuite { checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) - checkAll("Future", ApplicativeEvalTests[Future].applicativeEvalWithError[Int, Int, Int]) } From 605482f21082a513490789445e8b0cb419d42176 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Sat, 25 Mar 2017 11:05:12 -0400 Subject: [PATCH 6/6] Formatting --- laws/src/main/scala/cats/laws/MonadDeferLaws.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/MonadDeferLaws.scala b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala index 0f49e2ad5c..1b59163135 100644 --- a/laws/src/main/scala/cats/laws/MonadDeferLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadDeferLaws.scala @@ -25,7 +25,6 @@ trait MonadDeferLaws[F[_]] extends MonadLaws[F] { fa.flatMap(_ => fa) <-> F.pure(f(f(a, b), b)) } - lazy val flatMapStackSafety: IsEq[F[Int]] = { // tailRecM expressed with flatMap def loop[A, B](a: A)(f: A => F[Either[A, B]]): F[B] =