diff --git a/laws/src/main/scala/cats/laws/InjectKLaws.scala b/laws/src/main/scala/cats/laws/InjectKLaws.scala new file mode 100644 index 0000000000..01049c6980 --- /dev/null +++ b/laws/src/main/scala/cats/laws/InjectKLaws.scala @@ -0,0 +1,20 @@ +package cats +package laws + +trait InjectKLaws[F[_], G[_]] { + def injectK: InjectK[F, G] + + def injectKRoundTripInj[A](fa: F[A]): IsEq[Option[F[A]]] = + (injectK.prj compose injectK.inj).apply(fa) <-> Some(fa) + + def injectKRoundTripPrj[A](ga: G[A]): IsEq[Option[G[A]]] = + injectK.prj(ga) match { + case Some(fa) => (Some(injectK.inj(fa)): Option[G[A]]) <-> Some(ga) + case None => (None: Option[G[A]]) <-> None + } +} + +object InjectKLaws { + def apply[F[_], G[_]](implicit ev: InjectK[F, G]): InjectKLaws[F, G] = + new InjectKLaws[F, G]{ val injectK: InjectK[F, G] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/InjectKTests.scala b/laws/src/main/scala/cats/laws/discipline/InjectKTests.scala new file mode 100644 index 0000000000..a6eed7fe61 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/InjectKTests.scala @@ -0,0 +1,31 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop +import Prop._ +import org.typelevel.discipline.Laws + +trait InjectKTests[F[_], G[_]] extends Laws { + def laws: InjectKLaws[F, G] + + def injectK[A](implicit + ArbFA: Arbitrary[F[A]], + EqOptionFA: Eq[Option[F[A]]], + ArbGA: Arbitrary[G[A]], + EqOptionGA: Eq[Option[G[A]]] + ): RuleSet = + new DefaultRuleSet( + "injectK", + None, + "injectK round trip inj" -> forAll((fa: F[A]) => laws.injectKRoundTripInj(fa)), + "injectK round trip prj" -> forAll((ga: G[A]) => laws.injectKRoundTripPrj(ga)) + ) + +} + +object InjectKTests { + def apply[F[_], G[_]](implicit ev: InjectK[F, G]): InjectKTests[F, G] = + new InjectKTests[F, G] { val laws: InjectKLaws[F, G] = InjectKLaws[F, G] } +} diff --git a/tests/src/test/scala/cats/tests/InjectKTests.scala b/tests/src/test/scala/cats/tests/InjectKTests.scala index 612bc26b75..991c57952a 100644 --- a/tests/src/test/scala/cats/tests/InjectKTests.scala +++ b/tests/src/test/scala/cats/tests/InjectKTests.scala @@ -1,6 +1,7 @@ package cats import cats.data.EitherK +import cats.laws.discipline.{ InjectKTests => InjectKTypeclassTests } import cats.tests.CatsSuite import org.scalacheck._ @@ -20,6 +21,8 @@ class InjectKTests extends CatsSuite { implicit def test1AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test1Algebra[A]] = Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test1(s, f)) + + implicit def test1AlgebraEq[A](implicit ev: Eq[A]): Eq[Test1Algebra[A]] = Eq.fromUniversalEquals } sealed trait Test2Algebra[A] @@ -36,10 +39,18 @@ class InjectKTests extends CatsSuite { implicit def test2AlgebraArbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2Algebra[A]] = Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f)) + + implicit def test2AlgebraEq[A](implicit ev: Eq[A]): Eq[Test2Algebra[A]] = Eq.fromUniversalEquals } type T[A] = EitherK[Test1Algebra, Test2Algebra, A] + implicit def tArbitrary[A]( + implicit arb1: Arbitrary[Test1Algebra[A]], arb2: Arbitrary[Test2Algebra[A]] + ): Arbitrary[T[A]] = Arbitrary(Gen.oneOf( + arb1.arbitrary.map(EitherK.leftc(_): T[A]), + arb2.arbitrary.map(EitherK.rightc(_): T[A]))) + test("inj & prj") { def distr[F[_], A](f1: F[A], f2: F[A]) (implicit @@ -96,4 +107,6 @@ class InjectKTests extends CatsSuite { InjectK.catsReflexiveInjectKInstance[List].prj[Int](listIntNull) should ===(Some(listIntNull)) } + checkAll("InjectK[Test1Algebra, T]", InjectKTypeclassTests[Test1Algebra, T].injectK[String]) + checkAll("InjectK[Test2Algebra, T]", InjectKTypeclassTests[Test2Algebra, T].injectK[String]) }