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

Missing Contravariant instance for OptionT #2548

Merged
merged 9 commits into from
Oct 14, 2018
30 changes: 17 additions & 13 deletions core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,6 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 {
def show(f: Const[A, B]): String = f.show
}

implicit def catsDataContravariantMonoidalForConst[D: Monoid]: ContravariantMonoidal[Const[D, ?]] = new ContravariantMonoidal[Const[D, ?]] {
override def unit = Const.empty[D, Unit]
override def contramap[A, B](fa: Const[D, A])(f: B => A): Const[D, B] =
fa.retag[B]
override def product[A, B](fa: Const[D, A], fb: Const[D, B]): Const[D, (A, B)] =
fa.retag[(A, B)] combine fb.retag[(A, B)]
}

implicit def catsDataTraverseForConst[C]: Traverse[Const[C, ?]] = new Traverse[Const[C, ?]] {
def foldLeft[A, B](fa: Const[C, A], b: B)(f: (B, A) => B): B = b

Expand Down Expand Up @@ -126,6 +118,15 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 {

private[data] sealed abstract class ConstInstances0 extends ConstInstances1 {

implicit def catsDataContravariantMonoidalForConst[D: Monoid]: ContravariantMonoidal[Const[D, ?]] =
new ContravariantMonoidal[Const[D, ?]] {
override def unit = Const.empty[D, Unit]
override def contramap[A, B](fa: Const[D, A])(f: B => A): Const[D, B] =
fa.retag[B]
override def product[A, B](fa: Const[D, A], fb: Const[D, B]): Const[D, (A, B)] =
fa.retag[(A, B)] combine fb.retag[(A, B)]
}

implicit def catsDataCommutativeApplicativeForConst[C](implicit C: CommutativeMonoid[C]): CommutativeApplicative[Const[C, ?]] =
new ConstApplicative[C] with CommutativeApplicative[Const[C, ?]] { val C0: CommutativeMonoid[C] = C }
}
Expand All @@ -142,11 +143,6 @@ private[data] sealed abstract class ConstInstances2 extends ConstInstances3 {
def combine(x: Const[A, B], y: Const[A, B]): Const[A, B] = x combine y
}

implicit def catsDataContravariantForConst[C]: Contravariant[Const[C, ?]] = new Contravariant[Const[C, ?]] {
override def contramap[A, B](fa: Const[C, A])(f: (B) => A): Const[C, B] =
fa.retag[B]
}

implicit def catsDataPartialOrderForConst[A: PartialOrder, B]: PartialOrder[Const[A, B]] = new PartialOrder[Const[A, B]]{
def partialCompare(x: Const[A, B], y: Const[A, B]): Double =
x partialCompare y
Expand All @@ -171,13 +167,21 @@ private[data] sealed abstract class ConstInstances4 {

implicit def catsDataFunctorForConst[C]: Functor[Const[C, ?]] =
new ConstFunctor[C]{}

implicit def catsDataContravariantForConst[C]: Contravariant[Const[C, ?]] =
new ConstContravariant[C] {}
}

private[data] sealed trait ConstFunctor[C] extends Functor[Const[C, ?]] {
def map[A, B](fa: Const[C, A])(f: A => B): Const[C, B] =
fa.retag[B]
}

private[data] sealed trait ConstContravariant[C] extends Contravariant[Const[C, ?]] {
override def contramap[A, B](fa: Const[C, A])(f: B => A): Const[C, B] =
fa.retag[B]
}

private[data] sealed trait ConstApply[C] extends ConstFunctor[C] with Apply[Const[C, ?]] {

implicit def C0: Semigroup[C]
Expand Down
16 changes: 15 additions & 1 deletion core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] =
OptionT(F.map(value)(_.map(f)))

def contramap[B](f: B => A)(implicit F: Contravariant[F]): OptionT[F, B] =
OptionT {
F.contramap(value)(_ map f)
}

/**
* Modify the context `F` using transformation `f`.
*/
Expand Down Expand Up @@ -278,6 +283,9 @@ private[data] sealed abstract class OptionTInstances0 extends OptionTInstances1

implicit def catsDateFunctorFilterForOptionT[F[_]](implicit F0: Functor[F]): FunctorFilter[OptionT[F, ?]] =
new OptionTFunctorFilter[F] { implicit val F = F0 }

implicit def catsDataContravariantForOptionT[F[_]](implicit F0: Contravariant[F]): Contravariant[OptionT[F, ?]] =
new OptionTContravariant[F] { implicit val F = F0 }
}

private[data] sealed abstract class OptionTInstances1 extends OptionTInstances2 {
Expand All @@ -289,7 +297,6 @@ private[data] sealed abstract class OptionTInstances1 extends OptionTInstances2

implicit def catsDataMonadErrorForOptionT[F[_], E](implicit F0: MonadError[F, E]): MonadError[OptionT[F, ?], E] =
new OptionTMonadError[F, E] { implicit val F = F0 }

}

private[data] sealed abstract class OptionTInstances2 extends OptionTInstances3 {
Expand All @@ -308,6 +315,13 @@ private[data] trait OptionTFunctor[F[_]] extends Functor[OptionT[F, ?]] {
override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f)
}

private[data] sealed trait OptionTContravariant[F[_]] extends Contravariant[OptionT[F, ?]] {
implicit def F: Contravariant[F]

override def contramap[A, B](fa: OptionT[F, A])(f: B => A): OptionT[F, B] =
fa contramap f
}

private[data] trait OptionTMonad[F[_]] extends Monad[OptionT[F, ?]] {
implicit def F: Monad[F]

Expand Down
8 changes: 8 additions & 0 deletions tests/src/test/scala/cats/tests/ConstSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ class ConstSuite extends CatsSuite {
checkAll("PartialOrder[Const[Set[Int], String]]", PartialOrderTests[Const[Set[Int], String]].partialOrder)
checkAll("Order[Const[Int, String]]", OrderTests[Const[Int, String]].order)

{
implicitly[Invariant[Const[String, ?]]]
Invariant[Const[String, ?]]

checkAll("Const[String, Int]", InvariantTests[Const[String, ?]].invariant[Int, Int, Int])
checkAll("Invariant[Const[String, ?]]", SerializableTests.serializable(Invariant[Const[String, ?]]))
}

checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int])
checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]]))

Expand Down
18 changes: 18 additions & 0 deletions tests/src/test/scala/cats/tests/OptionTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cats.data.{Const, OptionT}
import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests, OrderTests, PartialOrderTests, EqTests}
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._

class OptionTSuite extends CatsSuite {
implicit val iso = SemigroupalTests.Isomorphisms.invariant[OptionT[ListWrapper, ?]](OptionT.catsDataFunctorForOptionT(ListWrapper.functor))
Expand Down Expand Up @@ -70,6 +71,23 @@ class OptionTSuite extends CatsSuite {
checkAll("Functor[OptionT[ListWrapper, ?]]", SerializableTests.serializable(Functor[OptionT[ListWrapper, ?]]))
}

{
// F has an Invariant
Invariant[Show]
Invariant[OptionT[Show, ?]]

checkAll("OptionT[Show, ?]", InvariantTests[OptionT[Show, ?]].invariant[Int, Int, Int])
checkAll("Invariant[OptionT[Show, ?]]", SerializableTests.serializable(Invariant[OptionT[Show, ?]]))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we'd have a check here for a type that has an Invariant instance but not a Contravariant instance to make sure that we cover this use-case. I think that Semigroup would be an example of such a type.


{
// F has a Contravariant
Contravariant[Show]
Contravariant[OptionT[Show, ?]]

checkAll("OptionT[Show, ?]", ContravariantTests[OptionT[Show, ?]].contravariant[Int, Int, Int])
checkAll("Contravariant[OptionT[Show, ?]]", SerializableTests.serializable(Contravariant[OptionT[Show, ?]]))
}

{
// F has a ContravariantMonoidal
Expand Down
9 changes: 9 additions & 0 deletions tests/src/test/scala/cats/tests/WriterTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,15 @@ class WriterTSuite extends CatsSuite {
checkAll("ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]]", SerializableTests.serializable(ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]]))
}

{
// F has an Invariant
Invariant[Show]
Invariant[WriterT[Show, Int, ?]]

checkAll("WriterT[Show, Int, ?]", InvariantTests[WriterT[Show, Int, ?]].invariant[Int, Int, Int])
checkAll("Invariant[WriterT[Show, Int, ?]]", SerializableTests.serializable(Invariant[WriterT[Show, Int, ?]]))
}

{
// F has a Foldable and L has a Monoid
implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int]
Expand Down