Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More instances for prod #1402

Merged
merged 11 commits into from
Oct 25, 2016
112 changes: 93 additions & 19 deletions core/src/main/scala/cats/data/Prod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
}

Copy link
Contributor

@kailuowang kailuowang Oct 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this particular case, to ensure the more specific instance having the highest priority, we basically want to ensure that instances of typeclasses who are lower in the inheritance tree are also lower in the instance trait inheritance tree. For example, Monad inherits Functor and thus is more "specific" than Functor, so it should be in an instance trait with higher priority (0 - 3) than where Functor is (4).
So, we need to rearrange the instances to that order.
MonadCombine <: Altenative
MonadCombine <: Monad <: Applicative <: Apply <: Functor
Traverse <: Foldable
Traverse <: Functor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out, I didn't think on that!

I've done the changes in 000a583

sealed trait ProdFunctor[F[_], G[_]] extends Functor[λ[α => Prod[F, G, α]]] {
Expand All @@ -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))
Expand Down Expand Up @@ -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)
}
3 changes: 3 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
48 changes: 48 additions & 0 deletions tests/src/test/scala/cats/tests/ProdTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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, ?]]
Expand All @@ -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])
Expand All @@ -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)})")
}
}

}