diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index 493ff8073b..3335535aef 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -2,6 +2,7 @@ package cats package data import cats.functor.Contravariant +import cats.syntax.cartesian._ /** * [[Prod]] is a product to two independent functor values. @@ -13,54 +14,74 @@ final case class Prod[F[_], G[_], A](first: F[A], second: G[A]) object Prod extends ProdInstances private[data] sealed abstract class ProdInstances extends ProdInstances0 { + implicit def catsDataMonadCombineForProd[F[_], G[_]](implicit FF: MonadCombine[F], GF: MonadCombine[G]): MonadCombine[λ[α => Prod[F, G, α]]] = new ProdMonadCombine[F, G] { + def F: MonadCombine[F] = FF + def G: MonadCombine[G] = GF + } + implicit def catsDataOrderForProd[F[_], G[_], A](implicit FF: Order[F[A]], GF: Order[G[A]]): Order[Prod[F, G, A]] = new ProdOrder[F, G, A] { + def F: Order[F[A]] = FF + def G: Order[G[A]] = GF + } + implicit def catsDataShowForProd[F[_], G[_], A](implicit FF: Show[F[A]], GF: Show[G[A]]): Show[Prod[F, G, A]] = new ProdShow[F, G, A] { + def F: Show[F[A]] = FF + def G: Show[G[A]] = GF + } + implicit def catsDataContravariantForProd[F[_], G[_]](implicit FC: Contravariant[F], GC: Contravariant[G]): Contravariant[λ[α => Prod[F, G, α]]] = new ProdContravariant[F, G] { + def F: Contravariant[F] = FC + def G: Contravariant[G] = GC + } +} + +private[data] sealed abstract class ProdInstances0 extends ProdInstances1 { + implicit def catsDataTraverseForProd[F[_], G[_]](implicit FF: Traverse[F], GF: Traverse[G]): Traverse[λ[α => Prod[F, G, α]]] = new ProdTraverse[F, G] { + def F: Traverse[F] = FF + def G: Traverse[G] = GF + } implicit def catsDataAlternativeForProd[F[_], G[_]](implicit FF: Alternative[F], GG: Alternative[G]): Alternative[λ[α => Prod[F, G, α]]] = new ProdAlternative[F, G] { def F: Alternative[F] = FF def G: Alternative[G] = GG } - + implicit def catsDataMonadForProd[F[_], G[_]](implicit FM: Monad[F], GM: Monad[G]): Monad[λ[α => Prod[F, G, α]]] = new ProdMonad[F, G] { + def F: Monad[F] = FM + def G: Monad[G] = GM + } implicit def catsDataEqForProd[F[_], G[_], A](implicit FF: Eq[F[A]], GG: Eq[G[A]]): Eq[Prod[F, G, A]] = new Eq[Prod[F, G, A]] { def eqv(x: Prod[F, G, A], y: Prod[F, G, A]): Boolean = FF.eqv(x.first, y.first) && GG.eqv(x.second, y.second) } } -private[data] sealed abstract class ProdInstances0 extends ProdInstances1 { +private[data] sealed abstract class ProdInstances1 extends ProdInstances2 { + implicit def catsDataFoldableForProd[F[_], G[_]](implicit FF: Foldable[F], GF: Foldable[G]): Foldable[λ[α => Prod[F, G, α]]] = new ProdFoldable[F, G] { + def F: Foldable[F] = FF + def G: Foldable[G] = GF + } implicit def catsDataMonoidKForProd[F[_], G[_]](implicit FF: MonoidK[F], GG: MonoidK[G]): MonoidK[λ[α => Prod[F, G, α]]] = new ProdMonoidK[F, G] { def F: MonoidK[F] = FF def G: MonoidK[G] = GG } -} - -private[data] sealed abstract class ProdInstances1 extends ProdInstances2 { - implicit def catsDataSemigroupKForProd[F[_], G[_]](implicit FF: SemigroupK[F], GG: SemigroupK[G]): SemigroupK[λ[α => Prod[F, G, α]]] = new ProdSemigroupK[F, G] { - def F: SemigroupK[F] = FF - def G: SemigroupK[G] = GG - } -} - -private[data] sealed abstract class ProdInstances2 extends ProdInstances3 { implicit def catsDataApplicativeForProd[F[_], G[_]](implicit FF: Applicative[F], GG: Applicative[G]): Applicative[λ[α => Prod[F, G, α]]] = new ProdApplicative[F, G] { def F: Applicative[F] = FF def G: Applicative[G] = GG } } -private[data] sealed abstract class ProdInstances3 extends ProdInstances4 { +private[data] sealed abstract class ProdInstances2 extends ProdInstances3 { + implicit def catsDataSemigroupKForProd[F[_], G[_]](implicit FF: SemigroupK[F], GG: SemigroupK[G]): SemigroupK[λ[α => Prod[F, G, α]]] = new ProdSemigroupK[F, G] { + def F: SemigroupK[F] = FF + def G: SemigroupK[G] = GG + } implicit def catsDataApplyForProd[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[λ[α => Prod[F, G, α]]] = new ProdApply[F, G] { def F: Apply[F] = FF def G: Apply[G] = GG } } -private[data] sealed abstract class ProdInstances4 { +private[data] sealed abstract class ProdInstances3 { implicit def catsDataFunctorForProd[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[λ[α => Prod[F, G, α]]] = new ProdFunctor[F, G] { def F: Functor[F] = FF def G: Functor[G] = GG } - implicit def catsDataContravariantForProd[F[_], G[_]](implicit FC: Contravariant[F], GC: Contravariant[G]): Contravariant[λ[α => Prod[F, G, α]]] = new ProdContravariant[F, G] { - def F: Contravariant[F] = FC - def G: Contravariant[G] = GC - } } sealed trait ProdFunctor[F[_], G[_]] extends Functor[λ[α => Prod[F, G, α]]] { @@ -78,7 +99,7 @@ sealed trait ProdContravariant[F[_], G[_]] extends Contravariant[λ[α => Prod[F sealed trait ProdApply[F[_], G[_]] extends Apply[λ[α => Prod[F, G, α]]] with ProdFunctor[F, G] { def F: Apply[F] def G: Apply[G] - def ap[A, B](f: Prod[F, G, A => B])(fa: Prod[F, G, A]): Prod[F, G, B] = + override def ap[A, B](f: Prod[F, G, A => B])(fa: Prod[F, G, A]): Prod[F, G, B] = Prod(F.ap(f.first)(fa.first), G.ap(f.second)(fa.second)) override def product[A, B](fa: Prod[F, G, A], fb: Prod[F, G, B]): Prod[F, G, (A, B)] = Prod(F.product(fa.first, fb.first), G.product(fa.second, fb.second)) @@ -109,3 +130,56 @@ sealed trait ProdAlternative[F[_], G[_]] extends Alternative[λ[α => Prod[F, G, def F: Alternative[F] def G: Alternative[G] } + +sealed trait ProdMonad[F[_], G[_]] extends Monad[λ[α => Prod[F, G, α]]] with ProdApplicative[F, G] { + def F: Monad[F] + def G: Monad[G] + override def pure[A](a: A): Prod[F, G, A] = + Prod(F.pure(a), G.pure(a)) + + override def flatMap[A, B](p: Prod[F, G, A])(f: A => Prod[F, G, B]): Prod[F, G, B] = + Prod(F.flatMap(p.first)(f(_).first), G.flatMap(p.second)(f(_).second)) + + def tailRecM[A, B](a: A)(f: A => Prod[F, G, Either[A, B]]): Prod[F, G, B] = + Prod(F.tailRecM(a)(f(_).first), G.tailRecM(a)(f(_).second)) +} + +sealed trait ProdFoldable[F[_], G[_]] extends Foldable[λ[α => Prod[F, G, α]]] { + def F: Foldable[F] + def G: Foldable[G] + + override def foldLeft[A, B](fa: Prod[F, G, A], b: B)(f: (B, A) => B): B = + G.foldLeft(fa.second, F.foldLeft(fa.first, b)(f))(f) + + override def foldRight[A, B](fa: Prod[F, G, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + F.foldRight(fa.first, G.foldRight(fa.second, lb)(f))(f) +} + +sealed trait ProdTraverse[F[_], G[_]] extends Traverse[λ[α => Prod[F, G, α]]] with ProdFoldable[F, G] { + def F: Traverse[F] + def G: Traverse[G] + + override def traverse[H[_]: Applicative, A, B](fa: Prod[F, G, A])(f: A => H[B]): H[Prod[F, G, B]] = + (F.traverse(fa.first)(f) |@| G.traverse(fa.second)(f)).map(Prod(_, _)) +} + +sealed trait ProdMonadCombine[F[_], G[_]] extends MonadCombine[λ[α => Prod[F, G, α]]] + with ProdMonad[F, G] with ProdAlternative[F, G] { + def F: MonadCombine[F] + def G: MonadCombine[G] +} + +sealed trait ProdShow[F[_], G[_], A] extends Show[Prod[F, G, A]] { + def F: Show[F[A]] + def G: Show[G[A]] + + def show(prod: Prod[F, G, A]): String = s"Prod(${F.show(prod.first)}, ${G.show(prod.second)})" +} + +sealed trait ProdOrder[F[_], G[_], A] extends Order[Prod[F, G, A]] { + def F: Order[F[A]] + def G: Order[G[A]] + + def compare(x: Prod[F, G, A], y: Prod[F, G, A]): Int = + Array(F.compare(x.first, y.first), G.compare(x.second, y.second)).find(_ != 0).getOrElse(0) +} diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index c9d2eb4d32..039a76fc35 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -130,6 +130,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsCogenForCoproduct[F[_], G[_], A](implicit F: Cogen[F[A]], G: Cogen[G[A]]): Cogen[Coproduct[F, G, A]] = Cogen((seed, x) => x.run.fold(F.perturb(seed, _), G.perturb(seed, _))) + implicit def catLawsCogenForProd[F[_], G[_], A](implicit F: Cogen[F[A]], G: Cogen[G[A]]): Cogen[Prod[F, G, A]] = + Cogen((seed, t) => F.perturb(G.perturb(seed, t.second), t.first)) + implicit def catsLawsArbitraryForShow[A: Arbitrary]: Arbitrary[Show[A]] = Arbitrary(Show.fromToString[A]) diff --git a/tests/src/test/scala/cats/tests/ProdTests.scala b/tests/src/test/scala/cats/tests/ProdTests.scala index 1d04e7bea4..751a3d184a 100644 --- a/tests/src/test/scala/cats/tests/ProdTests.scala +++ b/tests/src/test/scala/cats/tests/ProdTests.scala @@ -6,6 +6,7 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ +import cats.kernel.laws.OrderLaws class ProdTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[Prod[Option, List, ?]] @@ -18,6 +19,8 @@ class ProdTests extends CatsSuite { checkAll("Prod[Show, Order, Int]", ContravariantTests[λ[α => Prod[Show, Order, α]]].contravariant[Int, Int, Int]) checkAll("Contravariant[Prod[Show, Order, Int]]", SerializableTests.serializable(Contravariant[λ[α => Prod[Show, Order, α]]])) + checkAll("Show[Prod[Option, Option, Int]]", SerializableTests.serializable(Show[Prod[Option, Option, Int]])) + { implicit val monoidK = ListWrapper.monoidK checkAll("Prod[ListWrapper, ListWrapper, ?]", MonoidKTests[Prod[ListWrapper, ListWrapper, ?]].monoidK[Int]) @@ -42,4 +45,49 @@ class ProdTests extends CatsSuite { checkAll("Prod[ListWrapper, ListWrapper, ?]", FunctorTests[Prod[ListWrapper, ListWrapper, ?]].functor[Int, Int, Int]) checkAll("Functor[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Functor[Prod[ListWrapper, ListWrapper, ?]])) } + + { + implicit val monad = ListWrapper.monad + implicit val iso = CartesianTests.Isomorphisms.invariant[Prod[ListWrapper, ListWrapper, ?]] + checkAll("Prod[ListWrapper, ListWrapper, ?]", MonadTests[Prod[ListWrapper, ListWrapper, ?]].monad[Int, Int, Int]) + checkAll("Monad[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Monad[Prod[ListWrapper, ListWrapper, ?]])) + } + + { + implicit val foldable = ListWrapper.foldable + checkAll("Prod[ListWrapper, ListWrapper, ?]", FoldableTests[Prod[ListWrapper, ListWrapper, ?]].foldable[Int, Int]) + checkAll("Foldable[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Foldable[Prod[ListWrapper, ListWrapper, ?]])) + } + + { + implicit val traverse = ListWrapper.traverse + checkAll("Prod[ListWrapper, ListWrapper, ?]", TraverseTests[Prod[ListWrapper, ListWrapper, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Traverse[Prod[ListWrapper, ListWrapper, ?]])) + } + + { + implicit val monadCombine = ListWrapper.monadCombine + implicit val iso = CartesianTests.Isomorphisms.invariant[Prod[ListWrapper, ListWrapper, ?]] + checkAll("Prod[ListWrapper, ListWrapper, ?]", MonadCombineTests[Prod[ListWrapper, ListWrapper, ?]].monadCombine[Int, Int, Int]) + checkAll("MonadCombine[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(MonadCombine[Prod[ListWrapper, ListWrapper, ?]])) + } + + { + implicit val E = ListWrapper.eqv[Int] + implicit val O = ListWrapper.order[Int] + implicit val P = ListWrapper.partialOrder[Int] + + checkAll("Prod[ListWrapper, ListWrapper, Int]", OrderLaws[Prod[ListWrapper, ListWrapper, Int]].eqv) + checkAll("Prod[ListWrapper, ListWrapper, Int]", OrderLaws[Prod[ListWrapper, ListWrapper, Int]].order) + checkAll("Prod[ListWrapper, ListWrapper, Int]", OrderLaws[Prod[ListWrapper, ListWrapper, Int]].partialOrder) + } + + test("show") { + forAll { (l1: Option[Int], l2: Option[Int]) => + val prod = Prod(l1, l2) + + Show[Prod[Option, Option, Int]].show(prod) should === (s"Prod(${Show[Option[Int]].show(l1)}, ${Show[Option[Int]].show(l2)})") + } + } + }