diff --git a/core/src/main/scala/cats/functor/Contravariant.scala b/core/src/main/scala/cats/functor/Contravariant.scala index 5b90010e12..2f820c1f4d 100644 --- a/core/src/main/scala/cats/functor/Contravariant.scala +++ b/core/src/main/scala/cats/functor/Contravariant.scala @@ -29,7 +29,22 @@ import simulacrum.typeclass } } -object Contravariant { +object Contravariant extends KernelContravariantInstances + +/** + * Convariant instances for types that are housed in cats.kernel and therefore + * can't have instances for this type class in their companion objects. + */ +private[functor] sealed trait KernelContravariantInstances { + implicit val catsFunctorContravariantForEq: Contravariant[Eq] = + new Contravariant[Eq] { + /** Derive a `Eq` for `B` given a `Eq[A]` and a function `B => A`. + * + * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) + */ + def contramap[A, B](fa: Eq[A])(f: B => A): Eq[B] = fa.on(f) + } + implicit val catsFunctorContravariantForPartialOrder: Contravariant[PartialOrder] = new Contravariant[PartialOrder] { /** Derive a `PartialOrder` for `B` given a `PartialOrder[A]` and a function `B => A`. diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index b84ac0b7f9..120331d922 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -83,6 +83,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForFn0[A: Arbitrary]: Arbitrary[() => A] = Arbitrary(getArbitrary[A].map(() => _)) + implicit def catsLawsArbitraryForEq[A: Arbitrary]: Arbitrary[Eq[A]] = + Arbitrary(new Eq[A] { def eqv(x: A, y: A) = x.hashCode == y.hashCode }) + implicit def catsLawsArbitraryForPartialOrder[A: Arbitrary]: Arbitrary[PartialOrder[A]] = Arbitrary(Gen.oneOf( PartialOrder.from[A]((_: A, _: A) => Double.NaN), diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala index d606d1d9c2..113a393b1f 100644 --- a/laws/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/src/main/scala/cats/laws/discipline/Eq.scala @@ -31,6 +31,22 @@ object eq { } } + /** + * Create an approximation of Eq[Eq[A]] by generating 100 values for A + * and comparing the application of the two eqv functions + */ + implicit def catsLawsEqForEq[A](implicit arbA: Arbitrary[(A, A)]): Eq[Eq[A]] = new Eq[Eq[A]] { + def eqv(f: Eq[A], g: Eq[A]): Boolean = { + val samples = List.fill(100)(arbA.arbitrary.sample).collect { + case Some(a) => a + case None => sys.error("Could not generate arbitrary values to compare two Eq[A]") + } + samples.forall { + case (l, r) => f.eqv(l, r) == g.eqv(l, r) + } + } + } + /** * Create an approximation of Eq[PartialOrder[A]] by generating 100 values for A * and comparing the application of the two compare functions @@ -51,14 +67,14 @@ object eq { * Create an approximation of Eq[Order[A]] by generating 100 values for A * and comparing the application of the two compare functions */ - implicit def catsLawsEqForOrder[A](implicit arbA: Arbitrary[(A, A)], intEq: Eq[Int]): Eq[Order[A]] = new Eq[Order[A]] { + implicit def catsLawsEqForOrder[A](implicit arbA: Arbitrary[(A, A)]): Eq[Order[A]] = new Eq[Order[A]] { def eqv(f: Order[A], g: Order[A]): Boolean = { val samples = List.fill(100)(arbA.arbitrary.sample).collect { case Some(a) => a case None => sys.error("Could not generate arbitrary values to compare two Order[A]") } samples.forall { - case (l, r) => intEq.eqv(f.compare(l, r), g.compare(l, r)) + case (l, r) => f.compare(l, r) == g.compare(l, r) } } } diff --git a/tests/src/test/scala/cats/tests/KernelContravariantTests.scala b/tests/src/test/scala/cats/tests/KernelContravariantTests.scala index 92be09409e..67297510b0 100644 --- a/tests/src/test/scala/cats/tests/KernelContravariantTests.scala +++ b/tests/src/test/scala/cats/tests/KernelContravariantTests.scala @@ -7,6 +7,9 @@ import cats.laws.discipline.{ContravariantTests, SerializableTests} import cats.laws.discipline.eq._ class KernelContravariantTests extends CatsSuite { + checkAll("Contravariant[Eq]", ContravariantTests[Eq].contravariant[Int, Int, Int]) + checkAll("Contravariant[Eq]", SerializableTests.serializable(Contravariant[Eq])) + checkAll("Contravariant[PartialOrder]", ContravariantTests[PartialOrder].contravariant[Int, Int, Int]) checkAll("Contravariant[PartialOrder]", SerializableTests.serializable(Contravariant[PartialOrder])) diff --git a/tests/src/test/scala/cats/tests/ShowTests.scala b/tests/src/test/scala/cats/tests/ShowTests.scala new file mode 100644 index 0000000000..9e61d3486c --- /dev/null +++ b/tests/src/test/scala/cats/tests/ShowTests.scala @@ -0,0 +1,12 @@ +package cats +package tests + +import cats.functor.Contravariant +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.{ContravariantTests, SerializableTests} +import cats.laws.discipline.eq._ + +class ShowTests extends CatsSuite { + checkAll("Contravariant[Show]", ContravariantTests[Show].contravariant[Int, Int, Int]) + checkAll("Contravariant[Show]", SerializableTests.serializable(Contravariant[Show])) +}