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
97 changes: 95 additions & 2 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 Down Expand Up @@ -52,7 +53,7 @@ private[data] sealed abstract class ProdInstances3 extends ProdInstances4 {
}
}

private[data] sealed abstract class ProdInstances4 {
private[data] sealed abstract class ProdInstances4 extends ProdInstances5 {
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
Expand All @@ -63,6 +64,45 @@ private[data] sealed abstract class ProdInstances4 {
}
}

private[data] sealed abstract class ProdInstances5 extends ProdInstances6 {
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
}
}

private[data] sealed abstract class ProdInstances6 extends ProdInstances7 {
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
}
}

private[data] sealed abstract class ProdInstances7 extends ProdInstances8 {
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
}
}

private[data] sealed abstract class ProdInstances8 extends ProdInstances9 {
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
}
}

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

private[data] sealed abstract class ProdInstances9 {
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] {
Copy link
Contributor

Choose a reason for hiding this comment

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

This can probably be moved up to one sub class down from the Eq instance

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure!

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] {
Copy link
Contributor

Choose a reason for hiding this comment

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

This one can probably be moved up to ProdInstances right

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes!

def F: Show[F[A]] = FF
def G: Show[G[A]] = GF
}
}

sealed trait ProdFunctor[F[_], G[_]] extends Functor[λ[α => Prod[F, G, α]]] {
def F: Functor[F]
def G: Functor[G]
Expand All @@ -78,7 +118,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 +149,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)
}
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 @@ -18,6 +18,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 +44,50 @@ 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, ?]]))
}

test("order") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure!

forAll { t: Prod[Id, Id, Int] =>
val u: Prod[Id, Id, Int] = Prod(t.second, t.first)
val Prod(t1, t2) = t
val Prod(u1, u2) = u

Order[Prod[Id, Id, Int]].compare(t, u) should === (Order[(Int, Int)].compare((t1, t2), (u1, u2)))
}
}

test("show") {
forAll { (l1: Option[Int], l2: Option[Int]) =>
val showOptionInt = implicitly[Show[Option[Int]]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nitpicking, but this can just be val showOptionInt = Show[Option[Int]], right ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, you're right :)

val prod = Prod(l1, l2)

Show[Prod[Option, Option, Int]].show(prod) should === (s"Prod(${showOptionInt.show(l1)}, ${showOptionInt.show(l2)})")
}
}

}