From fa6457a61b527736d88a5192e9fae060db2b3ffe Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 22 Jun 2015 01:04:31 -0400 Subject: [PATCH] Add a Bimonad law. The law requires that extract(pure(a)) = a. This commit also updates the Monad* tests to use the new EqK typeclass, and also relocates some laws from ComonadLaws to CoflatMapLaws. There is a fair amount of plubming that had to change to get all of this working. A really important but thankless task will be to go through all of our tests and use EqK and ArbitraryK where possible to create a more consistent experience. This will only get harder once we have a new ScalaCheck release and we have to worry about Cogen as well. --- core/src/main/scala/cats/std/future.scala | 6 ++-- .../main/scala/cats/laws/BimonadLaws.scala | 17 +++++++++++ .../main/scala/cats/laws/CoflatMapLaws.scala | 11 ++++++- .../main/scala/cats/laws/ComonadLaws.scala | 9 ------ .../src/main/scala/cats/laws/MonadLaws.scala | 8 ++++- .../cats/laws/discipline/BimonadTests.scala | 30 +++++++++++++++++++ .../cats/laws/discipline/ComonadTests.scala | 13 ++++---- .../main/scala/cats/laws/discipline/EqK.scala | 30 +++++++++++++++---- .../laws/discipline/MonadCombineTests.scala | 24 ++++++++------- .../laws/discipline/MonadErrorTests.scala | 30 ++++++++++++------- .../laws/discipline/MonadFilterTests.scala | 20 ++++++------- .../laws/discipline/MonadReaderTests.scala | 13 ++++++-- .../laws/discipline/MonadStateTests.scala | 13 ++++++-- .../cats/laws/discipline/MonadTests.scala | 25 +++++++++------- .../scala/cats/laws/discipline/package.scala | 1 + .../test/scala/cats/tests/FutureTests.scala | 29 ++++++++++-------- .../test/scala/cats/tests/FunctionTests.scala | 7 ++--- 17 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 laws/shared/src/main/scala/cats/laws/BimonadLaws.scala create mode 100644 laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index 4e0a03bbec..2be0b41599 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -1,6 +1,8 @@ package cats package std +import cats.syntax.eq._ + import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.duration.FiniteDuration @@ -26,8 +28,8 @@ trait FutureInstances extends FutureInstances1 { def futureEq[A](atMost: FiniteDuration)(implicit A: Eq[A], ec: ExecutionContext): Eq[Future[A]] = new Eq[Future[A]] { - - def eqv(x: Future[A], y: Future[A]): Boolean = Await.result((x zip y).map((A.eqv _).tupled), atMost) + def eqv(fx: Future[A], fy: Future[A]): Boolean = + Await.result((fx zip fy).map { case (x, y) => x === y }, atMost) } } diff --git a/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala new file mode 100644 index 0000000000..9cbe8953f5 --- /dev/null +++ b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala @@ -0,0 +1,17 @@ +package cats +package laws + +/** + * Laws that must be obeyed by any [[Bimonad]]. + */ +trait BimonadLaws[F[_]] extends MonadLaws[F] with ComonadLaws[F] { + implicit override def F: Bimonad[F] + + def pureExtractComposition[A](a: A): IsEq[A] = + F.extract(F.pure(a)) <-> a +} + +object BimonadLaws { + def apply[F[_]](implicit ev: Bimonad[F]): BimonadLaws[F] = + new BimonadLaws[F] { def F: Bimonad[F] = ev } +} diff --git a/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala b/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala index 0685e985bf..7591959d8f 100644 --- a/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala @@ -2,7 +2,7 @@ package cats package laws import cats.data.Cokleisli -import cats.syntax.coflatMap._ +import cats.implicits._ /** * Laws that must be obeyed by any `CoflatMap`. @@ -13,6 +13,15 @@ trait CoflatMapLaws[F[_]] extends FunctorLaws[F] { def coflatMapAssociativity[A, B, C](fa: F[A], f: F[A] => B, g: F[B] => C): IsEq[F[C]] = fa.coflatMap(f).coflatMap(g) <-> fa.coflatMap(x => g(x.coflatMap(f))) + def coflattenThroughMap[A](fa: F[A]): IsEq[F[F[F[A]]]] = + fa.coflatten.coflatten <-> fa.coflatten.map(_.coflatten) + + def coflattenCoherence[A, B](fa: F[A], f: F[A] => B): IsEq[F[B]] = + fa.coflatMap(f) <-> fa.coflatten.map(f) + + def coflatMapIdentity[A, B](fa: F[A]): IsEq[F[F[A]]] = + fa.coflatten <-> fa.coflatMap(identity) + /** * The composition of `cats.data.Cokleisli` arrows is associative. This is * analogous to [[coflatMapAssociativity]]. diff --git a/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala b/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala index 961c4d9363..d594c44155 100644 --- a/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala @@ -16,15 +16,6 @@ trait ComonadLaws[F[_]] extends CoflatMapLaws[F] { def mapCoflattenIdentity[A](fa: F[A]): IsEq[F[A]] = fa.coflatten.map(_.extract) <-> fa - def coflattenThroughMap[A](fa: F[A]): IsEq[F[F[F[A]]]] = - fa.coflatten.coflatten <-> fa.coflatten.map(_.coflatten) - - def coflattenCoherence[A, B](fa: F[A], f: F[A] => B): IsEq[F[B]] = - fa.coflatMap(f) <-> fa.coflatten.map(f) - - def coflatMapIdentity[A, B](fa: F[A]): IsEq[F[F[A]]] = - fa.coflatten <-> fa.coflatMap(identity) - def mapCoflatMapCoherence[A, B](fa: F[A], f: A => B): IsEq[F[B]] = fa.map(f) <-> fa.coflatMap(fa0 => f(fa0.extract)) diff --git a/laws/shared/src/main/scala/cats/laws/MonadLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadLaws.scala index e7923c9630..79620c6318 100644 --- a/laws/shared/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/MonadLaws.scala @@ -2,7 +2,7 @@ package cats package laws import cats.data.Kleisli -import cats.syntax.flatMap._ +import cats.implicits._ /** * Laws that must be obeyed by any `Monad`. @@ -29,6 +29,12 @@ trait MonadLaws[F[_]] extends ApplicativeLaws[F] with FlatMapLaws[F] { */ def kleisliRightIdentity[A, B](a: A, f: A => F[B]): IsEq[F[B]] = (Kleisli(f) andThen Kleisli(F.pure[B])).run(a) <-> f(a) + + /** + * Make sure that map and flatMap are consistent. + */ + def mapFlatMapCoherence[A, B](fa: F[A], f: A => B): IsEq[F[B]] = + fa.flatMap(a => F.pure(f(a))) <-> fa.map(f) } object MonadLaws { diff --git a/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala new file mode 100644 index 0000000000..8cca83d3af --- /dev/null +++ b/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -0,0 +1,30 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop +import Prop._ + +trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { + def laws: BimonadLaws[F] + + def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = + new RuleSet { + def name: String = "bimonad" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monad[A, B, C], comonad[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "pure and extract compose" -> forAll(laws.pureExtractComposition[A] _) + ) + } +} + +object BimonadTests { + def apply[F[_]: Bimonad: ArbitraryK: EqK]: BimonadTests[F] = + new BimonadTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: BimonadLaws[F] = BimonadLaws[F] + } +} diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala index dfe7377d27..757acf657a 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala @@ -14,13 +14,12 @@ trait ComonadTests[F[_]] extends CoflatMapTests[F] { def laws: ComonadLaws[F] def comonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] - - implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] - implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize[F[A]] - implicit val eqfffa: Eq[F[F[F[A]]]] = EqK[F].synthesize[F[F[A]]] - implicit val eqfb: Eq[F[B]] = EqK[F].synthesize[B] - implicit val eqfc: Eq[F[C]] = EqK[F].synthesize[C] + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit val eqfa: Eq[F[A]] = EqK[F].synthesize + implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize + implicit val eqfffa: Eq[F[F[F[A]]]] = EqK[F].synthesize + implicit val eqfb: Eq[F[B]] = EqK[F].synthesize + implicit val eqfc: Eq[F[C]] = EqK[F].synthesize new DefaultRuleSet( name = "comonad", diff --git a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala index 4e8267c52e..21c2b50a29 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala @@ -2,12 +2,10 @@ package cats package laws package discipline -import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const} -import org.scalacheck.Arbitrary - +import cats.data._ import cats.implicits._ -import scala.concurrent.Future +import org.scalacheck.Arbitrary trait EqK[F[_]] { def synthesize[A: Eq]: Eq[F[A]] @@ -85,7 +83,6 @@ object EqK { implicit val vector: EqK[Vector] = new EqK[Vector] { def synthesize[A: Eq]: Eq[Vector[A]] = implicitly } - import cats.data.{Streaming, StreamingT} implicit val streaming: EqK[Streaming] = new EqK[Streaming] { def synthesize[A: Eq]: Eq[Streaming[A]] = implicitly } @@ -96,4 +93,27 @@ object EqK { implicitly } } + + implicit def function1L[A: Arbitrary]: EqK[A => ?] = + new EqK[A => ?] { + def synthesize[B: Eq]: Eq[A => B] = + cats.laws.discipline.eq.function1Eq + } + + implicit def kleisli[F[_]: EqK, A](implicit evKA: EqK[A => ?]): EqK[Kleisli[F, A, ?]] = + new EqK[Kleisli[F, A, ?]] { + def synthesize[B: Eq]: Eq[Kleisli[F, A, B]] = { + implicit val eqFB: Eq[F[B]] = EqK[F].synthesize[B] + implicit val eqAFB: Eq[A => F[B]] = evKA.synthesize[F[B]] + eqAFB.on[Kleisli[F, A, B]](_.run) + } + } + + implicit def optionT[F[_]: EqK]: EqK[OptionT[F, ?]] = + new EqK[OptionT[F, ?]] { + def synthesize[A: Eq]: Eq[OptionT[F, A]] = { + implicit val eqFOA: Eq[F[Option[A]]] = EqK[F].synthesize[Option[A]] + implicitly + } + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala index 8ee21bc4c8..653bad3a8f 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala @@ -9,15 +9,13 @@ import Prop._ trait MonadCombineTests[F[_]] extends MonadFilterTests[F] with AlternativeTests[F] { def laws: MonadCombineLaws[F] - def monadCombine[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]], - arbFAB: Arbitrary[F[A => B]] - ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] + def monadCombine[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize + implicit def ArbFAB: Arbitrary[F[A => B]] = ArbitraryK[F].synthesize + implicit def EqFA: Eq[F[A]] = EqK[F].synthesize + implicit def EqFB: Eq[F[B]] = EqK[F].synthesize + implicit def EqFC: Eq[F[C]] = EqK[F].synthesize new RuleSet { def name: String = "monadCombine" @@ -31,6 +29,10 @@ trait MonadCombineTests[F[_]] extends MonadFilterTests[F] with AlternativeTests[ } object MonadCombineTests { - def apply[F[_]: MonadCombine]: MonadCombineTests[F] = - new MonadCombineTests[F] { def laws: MonadCombineLaws[F] = MonadCombineLaws[F] } + def apply[F[_]: MonadCombine: ArbitraryK: EqK]: MonadCombineTests[F] = + new MonadCombineTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: MonadCombineLaws[F] = MonadCombineLaws[F] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index abc22453ec..5293684e2d 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -8,15 +8,17 @@ import org.scalacheck.Prop.forAll trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { def laws: MonadErrorLaws[F, E] - def monadError[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit - ArbF: ArbitraryK[F[E, ?]], - EqFA: Eq[F[E, A]], - EqFB: Eq[F[E, B]], - EqFC: Eq[F[E, C]], - ArbE: Arbitrary[E] - ): RuleSet = { - implicit def ArbFEA: Arbitrary[F[E, A]] = ArbF.synthesize[A] - implicit def ArbFEB: Arbitrary[F[E, B]] = ArbF.synthesize[B] + implicit def arbitraryK: ArbitraryK[F[E, ?]] + implicit def eqK: EqK[F[E, ?]] + + implicit def arbitraryE: Arbitrary[E] + implicit def eqE: Eq[E] + + def monadError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFEA: Arbitrary[F[E, A]] = arbitraryK.synthesize[A] + implicit def ArbFEB: Arbitrary[F[E, B]] = arbitraryK.synthesize[B] + implicit def EqFEA: Eq[F[E, A]] = eqK.synthesize[A] + implicit def EqFEB: Eq[F[E, B]] = eqK.synthesize[B] new RuleSet { def name: String = "monadError" @@ -32,6 +34,12 @@ trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { } object MonadErrorTests { - def apply[F[_, _], E](implicit FE: MonadError[F, E]): MonadErrorTests[F, E] = - new MonadErrorTests[F, E] { def laws: MonadErrorLaws[F, E] = MonadErrorLaws[F, E] } + def apply[F[_, _], E: Arbitrary: Eq](implicit FE: MonadError[F, E], ArbKFE: ArbitraryK[F[E, ?]], EqKFE: EqK[F[E, ?]]): MonadErrorTests[F, E] = + new MonadErrorTests[F, E] { + def arbitraryE: Arbitrary[E] = implicitly[Arbitrary[E]] + def arbitraryK: ArbitraryK[F[E, ?]] = ArbKFE + def eqE: Eq[E] = Eq[E] + def eqK: EqK[F[E, ?]] = EqKFE + def laws: MonadErrorLaws[F, E] = MonadErrorLaws[F, E] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala index c62984d1c0..af676ce691 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala @@ -9,14 +9,10 @@ import Prop._ trait MonadFilterTests[F[_]] extends MonadTests[F] { def laws: MonadFilterLaws[F] - def monadFilter[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]] - ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] + def monadFilter[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize + implicit def EqFB: Eq[F[B]] = EqK[F].synthesize new DefaultRuleSet( name = "monadFilter", @@ -27,6 +23,10 @@ trait MonadFilterTests[F[_]] extends MonadTests[F] { } object MonadFilterTests { - def apply[F[_]: MonadFilter]: MonadFilterTests[F] = - new MonadFilterTests[F] { def laws: MonadFilterLaws[F] = MonadFilterLaws[F] } + def apply[F[_]: MonadFilter: ArbitraryK: EqK]: MonadFilterTests[F] = + new MonadFilterTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: MonadFilterLaws[F] = MonadFilterLaws[F] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala index ace445ebda..9f61ccfdc6 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala @@ -8,7 +8,10 @@ import org.scalacheck.Prop.forAll trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { def laws: MonadReaderLaws[F, R] - def monadReader[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + implicit def arbitraryK: ArbitraryK[F[R, ?]] + implicit def eqK: EqK[F[R, ?]] + + def monadReader[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit ArbF: ArbitraryK[F[R, ?]], EqFA: Eq[F[R, A]], EqFB: Eq[F[R, B]], @@ -34,6 +37,10 @@ trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { } object MonadReaderTests { - def apply[F[_, _], R](implicit FR: MonadReader[F, R]): MonadReaderTests[F, R] = - new MonadReaderTests[F, R] { def laws: MonadReaderLaws[F, R] = MonadReaderLaws[F, R] } + def apply[F[_, _], R](implicit FR: MonadReader[F, R], arbKFR: ArbitraryK[F[R, ?]], eqKFR: EqK[F[R, ?]]): MonadReaderTests[F, R] = + new MonadReaderTests[F, R] { + def arbitraryK: ArbitraryK[F[R, ?]] = arbKFR + def eqK: EqK[F[R, ?]] = eqKFR + def laws: MonadReaderLaws[F, R] = MonadReaderLaws[F, R] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala index 83f9b9d29f..a0ef3c5e2c 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala @@ -8,7 +8,10 @@ import org.scalacheck.Prop.forAll trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { def laws: MonadStateLaws[F, S] - def monadState[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + implicit def arbitraryK: ArbitraryK[F[S, ?]] + implicit def eqK: EqK[F[S, ?]] + + def monadState[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit ArbF: ArbitraryK[F[S, ?]], EqFA: Eq[F[S, A]], EqFB: Eq[F[S, B]], @@ -35,6 +38,10 @@ trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { } object MonadStateTests { - def apply[F[_, _], S](implicit FS: MonadState[F, S]): MonadStateTests[F, S] = - new MonadStateTests[F, S] { def laws: MonadStateLaws[F, S] = MonadStateLaws[F, S] } + def apply[F[_, _], S](implicit FS: MonadState[F, S], arbKFS: ArbitraryK[F[S, ?]], eqKFS: EqK[F[S, ?]]): MonadStateTests[F, S] = + new MonadStateTests[F, S] { + def arbitraryK: ArbitraryK[F[S, ?]] = arbKFS + def eqK: EqK[F[S, ?]] = eqKFS + def laws: MonadStateLaws[F, S] = MonadStateLaws[F, S] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala index ef26006d9b..7b0a2a8e3d 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -9,14 +9,15 @@ import Prop._ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { def laws: MonadLaws[F] - def monad[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]] - ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] + implicit def arbitraryK: ArbitraryK[F] + implicit def eqK: EqK[F] + + def monad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize + implicit val eqfa: Eq[F[A]] = EqK[F].synthesize + implicit val eqfb: Eq[F[B]] = EqK[F].synthesize + implicit val eqfc: Eq[F[C]] = EqK[F].synthesize new RuleSet { def name: String = "monad" @@ -31,6 +32,10 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { } object MonadTests { - def apply[F[_]: Monad]: MonadTests[F] = - new MonadTests[F] { def laws: MonadLaws[F] = MonadLaws[F] } + def apply[F[_]: Monad: ArbitraryK: EqK]: MonadTests[F] = + new MonadTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: MonadLaws[F] = MonadLaws[F] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/package.scala b/laws/shared/src/main/scala/cats/laws/discipline/package.scala index 0366877815..a015ad9bcd 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/package.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/package.scala @@ -2,6 +2,7 @@ package cats package laws import algebra.laws._ + import org.scalacheck.Prop package object discipline { diff --git a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala b/tests/jvm/src/test/scala/cats/tests/FutureTests.scala index b84eedcb65..8563938736 100644 --- a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/tests/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -3,7 +3,8 @@ package tests import cats.data.Xor import cats.laws.discipline._ -import scala.concurrent.Future + +import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import scala.util.control.NonFatal @@ -14,27 +15,29 @@ import org.scalacheck.Arbitrary.arbitrary class FutureTests extends CatsSuite { val timeout = 3.seconds - implicit val eqkf: EqK[Future] = - new EqK[Future] { - def synthesize[A: Eq]: Eq[Future[A]] = futureEq(timeout) - } - def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } - implicit val eqv: Eq[Future[Int]] = - new Eq[Future[Int]] { - implicit val throwableEq: Eq[Throwable] = Eq.fromUniversalEquals - - def eqv(x: Future[Int], y: Future[Int]): Boolean = - futureEq[Xor[Throwable, Int]](timeout).eqv(futureXor(x), futureXor(y)) + implicit val eqkf: EqK[Future] = + new EqK[Future] { + def synthesize[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 + implicit val comonad: Comonad[Future] = futureComonad(timeout) // Need non-fatal Throwables for Future recoverWith/handleError implicit val nonFatalArbitrary: Arbitrary[Throwable] = - Arbitrary(arbitrary[Exception].map(e => e.asInstanceOf[Throwable])) + //Arbitrary(arbitrary[Exception].map(_.asInstanceOf[Throwable])) + Arbitrary(org.scalacheck.Gen.const(new Exception("hi there").asInstanceOf[Throwable])) checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) diff --git a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala index 6e313e71d9..39016e8794 100644 --- a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala @@ -7,11 +7,8 @@ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ class FunctionTests extends CatsSuite { - checkAll("Function0[Int]", ComonadTests[Function0].comonad[Int, Int, Int]) - checkAll("Comonad[Function0]", SerializableTests.serializable(Comonad[Function0])) - - checkAll("Function0[Int]", MonadTests[Function0].monad[Int, Int, Int]) - checkAll("Monad[Function0]", SerializableTests.serializable(Monad[Function0])) + checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) + checkAll("Bimonad[Function0]", SerializableTests.serializable(Comonad[Function0])) checkAll("Function1[Int, Int]", MonadReaderTests[Function1, Int].monadReader[Int, Int, Int]) checkAll("MonadReader[Function1, Int]", SerializableTests.serializable(MonadReader[Function1, Int]))