diff --git a/build.sbt b/build.sbt index 6a1b4bce06..368fe83509 100644 --- a/build.sbt +++ b/build.sbt @@ -198,6 +198,40 @@ def mimaSettings(moduleName: String) = Seq( exclude[MissingTypesProblem]("cats.data.OneAndLowPriority3"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority2"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority1"), + exclude[ReversedMissingMethodProblem]("cats.Contravariant.liftContravariant"), + exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalForEq"), + exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalForOrder"), + exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalForOrdering"), + exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalEquiv"), + exclude[ReversedMissingMethodProblem]("cats.ContravariantSemigroupal.contramap2"), + exclude[ReversedMissingMethodProblem]("cats.ContravariantSemigroupal#Ops.contramap2"), + exclude[InheritedNewAbstractMethodProblem]("cats.ContravariantMonoidal#ToContravariantMonoidalOps.toContravariantMonoidalOps"), + exclude[InheritedNewAbstractMethodProblem]("cats.ContravariantSemigroupal#ToContravariantSemigroupalOps.toContravariantSemigroupalOps"), + exclude[ReversedMissingMethodProblem]("cats.Applicative.composeContravariantMonoidal"), + exclude[UpdateForwarderBodyProblem]("cats.instances.Function1Instances.catsStdContravariantForFunction1"), + exclude[DirectMissingMethodProblem]("cats.instances.package=uiv.catsContravariantSemigroupalEquiv"), + exclude[DirectMissingMethodProblem]("cats.instances.OrderingInstances.catsContravariantSemigroupalForOrdering"), + exclude[ReversedMissingMethodProblem]("cats.instances.OrderingInstances.cats$instances$OrderingInstances$_setter_$catsContravariantMonoidalForOrdering_="), + exclude[ReversedMissingMethodProblem]("cats.instances.OrderingInstances.catsContravariantMonoidalForOrdering"), + exclude[DirectMissingMethodProblem]("cats.instances.OrderInstances.catsContravariantSemigroupalForOrder"), + exclude[ReversedMissingMethodProblem]("cats.instances.OrderInstances.catsContravariantMonoidalForOrder"), + exclude[ReversedMissingMethodProblem]("cats.instances.OrderInstances.cats$instances$OrderInstances$_setter_$catsContravariantMonoidalForOrder_="), + exclude[DirectMissingMethodProblem]("cats.instances.package#ordering.catsContravariantSemigroupalForOrdering"), + exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalForEq"), + exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalForOrder"), + exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalForOrdering"), + exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalEquiv"), + exclude[DirectMissingMethodProblem]("cats.instances.EquivInstances.catsContravariantSemigroupalEquiv"), + exclude[ReversedMissingMethodProblem]("cats.instances.EquivInstances.catsContravariantMonoidalForEquiv"), + exclude[ReversedMissingMethodProblem]("cats.instances.EquivInstances.cats$instances$EquivInstances$_setter_$catsContravariantMonoidalForEquiv_="), + exclude[DirectMissingMethodProblem]("cats.instances.package=.catsContravariantSemigroupalForEq"), + exclude[DirectMissingMethodProblem]("cats.instances.package#order.catsContravariantSemigroupalForOrder"), + exclude[ReversedMissingMethodProblem]("cats.instances.Function1Instances.catsStdContravariantMonoidalForFunction1"), + exclude[DirectMissingMethodProblem]("cats.instances.EqInstances.catsContravariantSemigroupalForEq"), + exclude[ReversedMissingMethodProblem]("cats.instances.EqInstances.catsContravariantMonoidalForEq"), + exclude[ReversedMissingMethodProblem]("cats.instances.EqInstances.cats$instances$EqInstances$_setter_$catsContravariantMonoidalForEq_="), + exclude[InheritedNewAbstractMethodProblem]("cats.syntax.ContravariantSemigroupalSyntax.catsSyntaxContravariantSemigroupal"), + exclude[InheritedNewAbstractMethodProblem]("cats.syntax.ContravariantMonoidalSyntax.catsSyntaxContravariantMonoidal"), exclude[DirectMissingMethodProblem]("cats.data.OneAndLowPriority3.catsDataNonEmptyTraverseForOneAnd"), exclude[DirectMissingMethodProblem]("cats.data.OneAndLowPriority2.catsDataTraverseForOneAnd"), exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdNonEmptyParallelForZipVector"), diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 889922d9cd..af0d5100e7 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -51,6 +51,12 @@ import simulacrum.typeclass val G = Applicative[G] } + def composeContravariantMonoidal[G[_]: ContravariantMonoidal]: ContravariantMonoidal[λ[α => F[G[α]]]] = + new ComposedApplicativeContravariantMonoidal[F, G] { + val F = self + val G = ContravariantMonoidal[G] + } + /** * Returns the given argument if `cond` is `false`, otherwise, unit lifted into F. */ diff --git a/core/src/main/scala/cats/Composed.scala b/core/src/main/scala/cats/Composed.scala index 6ed0c8a19d..0a6953e7be 100644 --- a/core/src/main/scala/cats/Composed.scala +++ b/core/src/main/scala/cats/Composed.scala @@ -113,6 +113,19 @@ private[cats] trait ComposedContravariantCovariant[F[_], G[_]] extends Contravar F.contramap(fga)(gb => G.map(gb)(f)) } +private[cats] trait ComposedApplicativeContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[λ[α => F[G[α]]]] { outer => + def F: Applicative[F] + def G: ContravariantMonoidal[G] + + override def unit[A]: F[G[A]] = F.pure(G.unit) + + override def contramap[A, B](fa: F[G[A]])(f: B => A): F[G[B]] = + F.map(fa)(G.contramap(_)(f)) + + override def product[A, B](fa: F[G[A]], fb: F[G[B]]): F[G[(A, B)]] = + F.map2(fa, fb)(G.product(_, _)) +} + private[cats] trait ComposedSemigroupal[F[_], G[_]] extends ContravariantSemigroupal[λ[α => F[G[α]]]] with ComposedContravariantCovariant[F, G] { outer => def F: ContravariantSemigroupal[F] def G: Functor[G] diff --git a/core/src/main/scala/cats/Contravariant.scala b/core/src/main/scala/cats/Contravariant.scala index 7956f1c965..1b08fb61f2 100644 --- a/core/src/main/scala/cats/Contravariant.scala +++ b/core/src/main/scala/cats/Contravariant.scala @@ -19,6 +19,8 @@ import simulacrum.typeclass */ def narrow[A, B <: A](fa: F[A]): F[B] = fa.asInstanceOf[F[B]] + def liftContravariant[A, B](f: A => B): F[B] => F[A] = contramap(_: F[B])(f) + override def composeFunctor[G[_]: Functor]: Contravariant[λ[α => F[G[α]]]] = new ComposedContravariantCovariant[F, G] { val F = self diff --git a/core/src/main/scala/cats/ContravariantMonoidal.scala b/core/src/main/scala/cats/ContravariantMonoidal.scala new file mode 100644 index 0000000000..f4f6881436 --- /dev/null +++ b/core/src/main/scala/cats/ContravariantMonoidal.scala @@ -0,0 +1,22 @@ +package cats + +import simulacrum.typeclass + +/** + * [[ContravariantMonoidal]] functors are functors that supply + * a unit along the diagonal map for the `contramap2` operation. + * + * Must obey the laws defined in cats.laws.ContravariantMonoidalLaws. + * + * Based on ekmett's contravariant library: + * https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html + */ +@typeclass trait ContravariantMonoidal[F[_]] extends ContravariantSemigroupal[F] { self => + /** + * `unit` produces an instance of `F` for any type `A` + * that is trivial with respect to `contramap2` along + * the diagonal + */ + def unit[A]: F[A] +} +object ContravariantMonoidal extends SemigroupalArityFunctions diff --git a/core/src/main/scala/cats/ContravariantSemigroupal.scala b/core/src/main/scala/cats/ContravariantSemigroupal.scala index e90d2d14ac..d641fe9067 100644 --- a/core/src/main/scala/cats/ContravariantSemigroupal.scala +++ b/core/src/main/scala/cats/ContravariantSemigroupal.scala @@ -13,3 +13,4 @@ import simulacrum.typeclass def G = Functor[G] } } +object ContravariantSemigroupal extends SemigroupalArityFunctions diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 147a2a3fe9..b076870bdf 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -65,9 +65,12 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { def show(f: Const[A, B]): String = f.show } - 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] = + implicit def catsDataContravariantMonoidalForConst[D: Monoid]: ContravariantMonoidal[Const[D, ?]] = new ContravariantMonoidal[Const[D, ?]] { + override def unit[A] = Const.empty[D, A] + 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, ?]] { @@ -102,11 +105,15 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { } private[data] sealed abstract class ConstInstances0 extends ConstInstances1 { - implicit def catsDataSemigroupForConst[A: Semigroup, B]: Semigroup[Const[A, B]] = new Semigroup[Const[A, B]] { 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 diff --git a/core/src/main/scala/cats/data/IdT.scala b/core/src/main/scala/cats/data/IdT.scala index c2fb5a4cce..7a79b014dc 100644 --- a/core/src/main/scala/cats/data/IdT.scala +++ b/core/src/main/scala/cats/data/IdT.scala @@ -73,6 +73,18 @@ private[data] sealed trait IdTApplicative[F[_]] extends Applicative[IdT[F, ?]] w def pure[A](a: A): IdT[F, A] = IdT.pure(a) } +private[data] sealed trait IdTContravariantMonoidal[F[_]] extends ContravariantMonoidal[IdT[F, ?]] { + implicit val F0: ContravariantMonoidal[F] + + override def unit[A]: IdT[F, A] = IdT(F0.unit[A]) + + override def contramap[A, B](fa: IdT[F, A])(f: B => A): IdT[F, B] = + IdT(F0.contramap(fa.value)(f)) + + override def product[A, B](fa: IdT[F, A], fb: IdT[F, B]): IdT[F, (A, B)] = + IdT(F0.product(fa.value, fb.value)) +} + private[data] sealed trait IdTFlatMap[F[_]] extends FlatMap[IdT[F, ?]] with IdTApply[F] { implicit val F0: FlatMap[F] @@ -123,7 +135,12 @@ private[data] sealed trait IdTNonEmptyTraverse[F[_]] extends IdTTraverse[F] with fa.reduceRightTo(f)(g) } -private[data] sealed abstract class IdTInstances5 { +private[data] sealed abstract class IdTInstances6 { + implicit def catsDataContravariantMonoidalForIdT[F[_]](implicit F: ContravariantMonoidal[F]): ContravariantMonoidal[IdT[F, ?]] = + new IdTContravariantMonoidal[F] { implicit val F0: ContravariantMonoidal[F] = F } +} + +private[data] sealed abstract class IdTInstances5 extends IdTInstances6 { implicit def catsDataFunctorForIdT[F[_]](implicit F: Functor[F]): Functor[IdT[F, ?]] = new IdTFunctor[F] { implicit val F0: Functor[F] = F } } diff --git a/core/src/main/scala/cats/data/IndexedStateT.scala b/core/src/main/scala/cats/data/IndexedStateT.scala index c599eacf24..5bef3ed408 100644 --- a/core/src/main/scala/cats/data/IndexedStateT.scala +++ b/core/src/main/scala/cats/data/IndexedStateT.scala @@ -230,6 +230,10 @@ private[data] sealed abstract class IndexedStateTInstances extends IndexedStateT implicit def catsDataAlternativeForIndexedStateT[F[_], S](implicit FM: Monad[F], FA: Alternative[F]): Alternative[IndexedStateT[F, S, S, ?]] with Monad[IndexedStateT[F, S, S, ?]] = new IndexedStateTAlternative[F, S] { implicit def F = FM; implicit def G = FA } + + implicit def catsDataContravariantMonoidalForIndexedStateT[F[_], S](implicit FD: ContravariantMonoidal[F], + FA: Applicative[F]): ContravariantMonoidal[IndexedStateT[F, S, S, ?]] = + new IndexedStateTContravariantMonoidal[F, S] { implicit def F = FD; implicit def G = FA } } private[data] sealed abstract class IndexedStateTInstances1 extends IndexedStateTInstances2 { @@ -362,6 +366,28 @@ private[data] sealed abstract class IndexedStateTSemigroupK[F[_], SA, SB] extend IndexedStateT(s => G.combineK(x.run(s), y.run(s))) } +private[data] sealed abstract class IndexedStateTContravariantMonoidal[F[_], S] extends ContravariantMonoidal[IndexedStateT[F, S, S, ?]]{ + implicit def F: ContravariantMonoidal[F] + implicit def G: Applicative[F] + + override def unit[A]: IndexedStateT[F, S, S, A] = + IndexedStateT.applyF(G.pure((s: S) => F.unit[(S, A)])) + + override def contramap[A, B](fa: IndexedStateT[F, S, S, A])(f: B => A): IndexedStateT[F, S, S, B] = + contramap2(fa, unit)(((a: A) => (a, a)) compose f) + + override def product[A, B](fa: IndexedStateT[F, S, S, A], fb: IndexedStateT[F, S, S, B]): IndexedStateT[F, S, S, (A, B)] = + contramap2(fa, fb)(identity) + + def contramap2[A, B, C](fb: IndexedStateT[F, S, S, B], fc: IndexedStateT[F, S, S, C])(f: A => (B, C)): IndexedStateT[F, S, S, A] = + IndexedStateT.applyF( + G.pure((s: S) => + ContravariantMonoidal.contramap2(G.map(fb.runF)(_.apply(s)), G.map(fc.runF)(_.apply(s)))( + (tup: (S, A)) => f(tup._2) match { + case (b, c) => (G.pure((tup._1, b)), G.pure((tup._1, c))) + })(G, F))) +} + private[data] sealed abstract class IndexedStateTAlternative[F[_], S] extends IndexedStateTMonad[F, S] with Alternative[IndexedStateT[F, S, S, ?]] { def G: Alternative[F] diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index d94ebb256b..25ba648f42 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -136,11 +136,8 @@ private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 implicit val catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] = catsDataCommutativeArrowForKleisli[Id] - implicit def catsDataContravariantForKleisli[F[_], C]: Contravariant[Kleisli[F, ?, C]] = - new Contravariant[Kleisli[F, ?, C]] { - override def contramap[A, B](fa: Kleisli[F, A, C])(f: B => A): Kleisli[F, B, C] = - fa.local(f) - } + implicit def catsDataContravariantMonoidalForKleisli[F[_], A](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[Kleisli[F, A, ?]] = + new KleisliContravariantMonoidal[F, A] { def F: ContravariantMonoidal[F] = F0 } } private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 { @@ -160,6 +157,12 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 def parallel: Kleisli[M, A, ?] ~> Kleisli[F, A, ?] = λ[Kleisli[M, A, ?] ~> Kleisli[F, A, ?]](_.mapK(P.parallel)) } + + implicit def catsDataContravariantForKleisli[F[_], C]: Contravariant[Kleisli[F, ?, C]] = + new Contravariant[Kleisli[F, ?, C]] { + override def contramap[A, B](fa: Kleisli[F, A, C])(f: B => A): Kleisli[F, B, C] = + fa.local(f) + } } private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 { @@ -294,6 +297,18 @@ private[data] trait KleisliAlternative[F[_], A] extends Alternative[Kleisli[F, A implicit def F: Alternative[F] } +private[data] sealed trait KleisliContravariantMonoidal[F[_], D] extends ContravariantMonoidal[Kleisli[F, D, ?]] { + implicit def F: ContravariantMonoidal[F] + + override def unit[A]: Kleisli[F, D, A] = Kleisli(Function.const(F.unit[A])) + + override def contramap[A, B](fa: Kleisli[F, D, A])(f: B => A): Kleisli[F, D, B] = + Kleisli(d => F.contramap(fa.run(d))(f)) + + override def product[A, B](fa: Kleisli[F, D, A], fb: Kleisli[F, D, B]): Kleisli[F, D, (A, B)] = + Kleisli(d => F.product(fa.run(d), fb.run(d))) +} + private[data] trait KleisliMonadError[F[_], A, E] extends MonadError[Kleisli[F, A, ?], E] with KleisliApplicativeError[F, A, E] with KleisliMonad[F, A] { def F: MonadError[F, E] } diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index 23acf56332..1d8aef27c5 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -43,6 +43,11 @@ private[data] sealed abstract class NestedInstances extends NestedInstances0 { new NestedNonEmptyTraverse[F, G] { val FG: NonEmptyTraverse[λ[α => F[G[α]]]] = NonEmptyTraverse[F].compose[G] } + + implicit def catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal]: ContravariantMonoidal[Nested[F, G, ?]] = + new NestedContravariantMonoidal[F, G] { + val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = Applicative[F].composeContravariantMonoidal[G] + } } private[data] sealed abstract class NestedInstances0 extends NestedInstances1 { @@ -263,3 +268,15 @@ private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested def contramap[A, B](fga: Nested[F, G, A])(f: B => A): Nested[F, G, B] = Nested(FG.contramap(fga.value)(f)) } + +private[data] trait NestedContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[Nested[F, G, ?]] { + def FG: ContravariantMonoidal[λ[α => F[G[α]]]] + + def unit[A]: Nested[F, G, A] = Nested(FG.unit) + + def contramap[A, B](fa: Nested[F, G, A])(f: B => A): Nested[F, G, B] = + Nested(FG.contramap(fa.value)(f)) + + def product[A, B](fa: Nested[F, G, A], fb: Nested[F, G, B]): Nested[F, G, (A, B)] = + Nested(FG.product(fa.value, fb.value)) +} diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index cf59218c8f..0acd5fc2d0 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -221,6 +221,9 @@ private[data] sealed abstract class OptionTInstances0 extends OptionTInstances1 implicit def catsDataMonadErrorForOptionT[F[_], E](implicit F0: MonadError[F, E]): MonadError[OptionT[F, ?], E] = new OptionTMonadError[F, E] { implicit val F = F0 } + implicit def catsDataContravariantMonoidalForOptionT[F[_]](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[OptionT[F, ?]] = + new OptionTContravariantMonoidal[F] { implicit val F = F0 } + implicit def catsDataSemigroupKForOptionT[F[_]](implicit F0: Monad[F]): SemigroupK[OptionT[F, ?]] = new OptionTSemigroupK[F] { implicit val F = F0 } @@ -281,6 +284,23 @@ private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] wi OptionT(F.handleErrorWith(fa.value)(f(_).value)) } +private trait OptionTContravariantMonoidal[F[_]] extends ContravariantMonoidal[OptionT[F, ?]] { + def F: ContravariantMonoidal[F] + + override def unit[A]: OptionT[F, A] = OptionT (F.unit) + + override def contramap[A, B](fa: OptionT[F, A])(f: B => A): OptionT[F, B] = + OptionT(F.contramap(fa.value)(_ map f)) + + override def product[A, B](fa: OptionT[F, A], fb: OptionT[F, B]): OptionT[F, (A, B)] = + OptionT(F.contramap(F.product(fa.value, fb.value))( + (t: Option[(A, B)]) => t match { + case Some((x, y)) => (Some(x), Some(y)) + case None => (None, None) + } + )) +} + private[data] trait OptionTFoldable[F[_]] extends Foldable[OptionT[F, ?]] { implicit def F: Foldable[F] diff --git a/core/src/main/scala/cats/data/Tuple2K.scala b/core/src/main/scala/cats/data/Tuple2K.scala index ab246e454f..dbed5a0283 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -29,10 +29,11 @@ private[data] sealed abstract class Tuple2KInstances extends Tuple2KInstances0 { def F: Show[F[A]] = FF def G: Show[G[A]] = GF } - implicit def catsDataContravariantForTuple2K[F[_], G[_]](implicit FC: Contravariant[F], GC: Contravariant[G]): Contravariant[λ[α => Tuple2K[F, G, α]]] = new Tuple2KContravariant[F, G] { - def F: Contravariant[F] = FC - def G: Contravariant[G] = GC - } + implicit def catsDataContravariantMonoidalForTuple2k[F[_], G[_]](implicit FD: ContravariantMonoidal[F], GD: ContravariantMonoidal[G]): ContravariantMonoidal[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KContravariantMonoidal[F, G] { + def F: ContravariantMonoidal[F] = FD + def G: ContravariantMonoidal[G] = GD + } } private[data] sealed abstract class Tuple2KInstances0 extends Tuple2KInstances1 { @@ -40,6 +41,10 @@ private[data] sealed abstract class Tuple2KInstances0 extends Tuple2KInstances1 def F: Traverse[F] = FF def G: Traverse[G] = GF } + implicit def catsDataContravariantForTuple2K[F[_], G[_]](implicit FC: Contravariant[F], GC: Contravariant[G]): Contravariant[λ[α => Tuple2K[F, G, α]]] = new Tuple2KContravariant[F, G] { + def F: Contravariant[F] = FC + def G: Contravariant[G] = GC + } implicit def catsDataEqForTuple2K[F[_], G[_], A](implicit FF: Eq[F[A]], GG: Eq[G[A]]): Eq[Tuple2K[F, G, A]] = new Eq[Tuple2K[F, G, A]] { def eqv(x: Tuple2K[F, G, A], y: Tuple2K[F, G, A]): Boolean = FF.eqv(x.first, y.first) && GG.eqv(x.second, y.second) @@ -123,6 +128,16 @@ private[data] sealed trait Tuple2KContravariant[F[_], G[_]] extends Contravarian def contramap[A, B](fa: Tuple2K[F, G, A])(f: B => A): Tuple2K[F, G, B] = Tuple2K(F.contramap(fa.first)(f), G.contramap(fa.second)(f)) } +private[data] sealed trait Tuple2KContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[λ[α => Tuple2K[F, G, α]]] { + def F: ContravariantMonoidal[F] + def G: ContravariantMonoidal[G] + def unit[A]: Tuple2K[F, G, A] = Tuple2K(F.unit, G.unit) + def product[A, B](fa: Tuple2K[F, G, A], fb: Tuple2K[F, G, B]): Tuple2K[F, G, (A, B)] = + Tuple2K(F.product(fa.first, fb.first), G.product(fa.second, fb.second)) + def contramap[A, B](fa: Tuple2K[F, G, A])(f: B => A): Tuple2K[F, G, B] = + Tuple2K(F.contramap(fa.first)(f), G.contramap(fa.second)(f)) +} + private[data] sealed trait Tuple2KApply[F[_], G[_]] extends Apply[λ[α => Tuple2K[F, G, α]]] with Tuple2KFunctor[F, G] { def F: Apply[F] def G: Apply[G] diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 61d88444ac..2bfa785108 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -178,6 +178,11 @@ private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 implicit val F0: Alternative[F] = F implicit val L0: Monoid[L] = L } + + implicit def catsDataContravariantMonoidalForWriterT[F[_], L](implicit F: ContravariantMonoidal[F]): ContravariantMonoidal[WriterT[F, L, ?]] = + new WriterTContravariantMonoidal[F, L] { + implicit val F0: ContravariantMonoidal[F] = F + } } private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 { @@ -353,6 +358,23 @@ private[data] sealed trait WriterTAlternative[F[_], L] extends Alternative[Write override implicit def F0: Alternative[F] } +private[data] sealed trait WriterTContravariantMonoidal[F[_], L] extends ContravariantMonoidal[WriterT[F, L, ?]] { + implicit def F0: ContravariantMonoidal[F] + + override def unit[A]: WriterT[F, L, A] = WriterT(F0.unit[(L, A)]) + + override def contramap[A, B](fa: WriterT[F, L, A])(f: B => A): WriterT[F, L, B] = + WriterT(F0.contramap(fa.run)((d: (L, B)) => (d._1, f(d._2)))) + + override def product[A, B](fa: WriterT[F, L, A], fb: WriterT[F, L, B]): WriterT[F, L, (A, B)] = + WriterT( + F0.contramap( + F0.product(fa.run, fb.run))( + (t: (L, (A, B))) => t match { + case (l, (a, b)) => ((l, a), (l, b)) + })) +} + private[data] sealed trait WriterTSemigroup[F[_], L, A] extends Semigroup[WriterT[F, L, A]] { implicit def F0: Semigroup[F[(L, A)]] diff --git a/core/src/main/scala/cats/instances/eq.scala b/core/src/main/scala/cats/instances/eq.scala index ea392f688b..dc304686d5 100644 --- a/core/src/main/scala/cats/instances/eq.scala +++ b/core/src/main/scala/cats/instances/eq.scala @@ -2,11 +2,24 @@ package cats package instances trait EqInstances { - implicit val catsContravariantSemigroupalForEq: ContravariantSemigroupal[Eq] = - new ContravariantSemigroupal[Eq] { - def contramap[A, B](fa: Eq[A])(fn: B => A): Eq[B] = Eq.by[B, A](fn)(fa) + implicit val catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] = + new ContravariantMonoidal[Eq] { + /** + * Defaults to the trivial equivalence relation + * contracting the type to a point + */ + def unit[A]: Eq[A] = Eq.allEqual + + /** Derive an `Eq` for `B` given an `Eq[A]` and a function `B => A`. + * + * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) + */ + def contramap[A, B](fa: Eq[A])(f: B => A): Eq[B] = + Eq.by(f)(fa) def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] = - Eq.instance { (left, right) => fa.eqv(left._1, right._1) && fb.eqv(left._2, right._2) } + Eq.instance { (left, right) => + fa.eqv(left._1, right._1) && fb.eqv(left._2, right._2) + } } } diff --git a/core/src/main/scala/cats/instances/equiv.scala b/core/src/main/scala/cats/instances/equiv.scala index 736fdaa12e..012d189ff7 100644 --- a/core/src/main/scala/cats/instances/equiv.scala +++ b/core/src/main/scala/cats/instances/equiv.scala @@ -2,17 +2,30 @@ package cats package instances trait EquivInstances { - implicit val catsContravariantSemigroupalEquiv: ContravariantSemigroupal[Equiv] = - new ContravariantSemigroupal[Equiv] { + implicit val catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] = + new ContravariantMonoidal[Equiv] { + /** + * Defaults to trivially contracting the type + * to a point + */ + def unit[A]: Equiv[A] = new Equiv[A] { + def equiv(x: A, y: A): Boolean = true + } + + /** Derive an `Equiv` for `B` given an `Equiv[A]` and a function `B => A`. + * + * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) + */ def contramap[A, B](fa: Equiv[A])(f: B => A): Equiv[B] = new Equiv[B] { - def equiv(x: B, y: B): Boolean = fa.equiv(f(x), f(y)) + def equiv(l: B, r: B): Boolean = + fa.equiv(f(l), f(r)) } def product[A, B](fa: Equiv[A], fb: Equiv[B]): Equiv[(A, B)] = new Equiv[(A, B)] { - def equiv(x: (A, B), y: (A, B)): Boolean = - fa.equiv(x._1, y._1) && fb.equiv(x._2, y._2) + def equiv(l: (A, B), r: (A, B)): Boolean = + fa.equiv(l._1, r._1) && fb.equiv(l._2, r._2) } } } diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index ede97d7857..434d0f531f 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -11,7 +11,6 @@ trait FunctionInstances extends cats.kernel.instances.FunctionInstances with Function0Instances with Function1Instances private[instances] sealed trait Function0Instances { - implicit val catsStdBimonadForFunction0: Bimonad[Function0] = new Bimonad[Function0] { def extract[A](x: () => A): A = x() @@ -36,11 +35,16 @@ private[instances] sealed trait Function0Instances { } } -private[instances] sealed trait Function1Instances { - implicit def catsStdContravariantForFunction1[R]: Contravariant[? => R] = - new Contravariant[? => R] { - def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R = - fa.compose(f) +private[instances] sealed trait Function1Instances extends Function1Instances0 { + implicit def catsStdContravariantMonoidalForFunction1[R: Monoid]: ContravariantMonoidal[? => R] = + new ContravariantMonoidal[? => R] { + def unit[A]: A => R = Function.const(Monoid[R].empty) + def contramap[A, B](fa: A => R)(f: B => A): B => R = + fa compose f + def product[A, B](fa: A => R, fb: B => R): ((A, B)) => R = + (ab: (A, B)) => ab match { + case (a, b) => Monoid[R].combine(fa(a), fb(b)) + } } implicit def catsStdMonadForFunction1[T1]: Monad[T1 => ?] = @@ -89,3 +93,12 @@ private[instances] sealed trait Function1Instances { implicit val catsStdMonoidKForFunction1: MonoidK[λ[α => Function1[α, α]]] = Category[Function1].algebraK } + + +private[instances] sealed trait Function1Instances0 { + implicit def catsStdContravariantForFunction1[R]: Contravariant[? => R] = + new Contravariant[? => R] { + def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R = + fa.compose(f) + } +} diff --git a/core/src/main/scala/cats/instances/order.scala b/core/src/main/scala/cats/instances/order.scala index e0dea80414..8a239d264c 100644 --- a/core/src/main/scala/cats/instances/order.scala +++ b/core/src/main/scala/cats/instances/order.scala @@ -2,14 +2,18 @@ package cats package instances trait OrderInstances extends cats.kernel.OrderToOrderingConversion { - - implicit val catsContravariantSemigroupalForOrder: ContravariantSemigroupal[Order] = - new ContravariantSemigroupal[Order] { + implicit val catsContravariantMonoidalForOrder: ContravariantMonoidal[Order] = + new ContravariantMonoidal[Order] { + /** + * Provides trivial order + */ + def unit[A]: Order[A] = Order.from[A]((x: A, y: A) => 0) /** Derive an `Order` for `B` given an `Order[A]` and a function `B => A`. * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ - def contramap[A, B](fa: Order[A])(f: B => A): Order[B] = Order.by[B, A](f)(fa) + def contramap[A, B](fa: Order[A])(f: B => A): Order[B] = + Order.by(f)(fa) def product[A, B](fa: Order[A], fb: Order[B]): Order[(A, B)] = new Order[(A, B)] { diff --git a/core/src/main/scala/cats/instances/ordering.scala b/core/src/main/scala/cats/instances/ordering.scala index 13240b7cbf..5afd3b58e7 100644 --- a/core/src/main/scala/cats/instances/ordering.scala +++ b/core/src/main/scala/cats/instances/ordering.scala @@ -2,13 +2,16 @@ package cats package instances trait OrderingInstances { - - implicit val catsContravariantSemigroupalForOrdering: ContravariantSemigroupal[Ordering] = - new ContravariantSemigroupal[Ordering] { - /** Derive an `Ordering` for `B` given an `Ordering[A]` and a function `B => A`. - * + implicit val catsContravariantMonoidalForOrdering: ContravariantMonoidal[Ordering] = + new ContravariantMonoidal[Ordering] { + /** * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ + + def unit[A]: Ordering[A] = new Ordering[A] { + def compare(l: A, r: A): Int = 0 + } + def contramap[A, B](fa: Ordering[A])(f: B => A): Ordering[B] = fa.on(f) def product[A, B](fa: Ordering[A], fb: Ordering[B]): Ordering[(A, B)] = diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 1c930bb26f..e2ae40e6c5 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -15,6 +15,8 @@ trait AllSyntax with ComonadSyntax with ComposeSyntax with ContravariantSyntax + with ContravariantMonoidalSyntax + with ContravariantSemigroupalSyntax with EitherKSyntax with EitherSyntax with EqSyntax diff --git a/core/src/main/scala/cats/syntax/contravariantMonoidal.scala b/core/src/main/scala/cats/syntax/contravariantMonoidal.scala new file mode 100644 index 0000000000..2e4f62a5c3 --- /dev/null +++ b/core/src/main/scala/cats/syntax/contravariantMonoidal.scala @@ -0,0 +1,15 @@ +package cats +package syntax + +import cats.ContravariantMonoidal + +trait ContravariantMonoidalSyntax { + implicit final def catsSyntaxContravariantMonoidal[F[_], A](fa: F[A])(implicit F: ContravariantMonoidal[F]): ContravariantMonoidalOps[F, A] = + new ContravariantMonoidalOps[F, A] { + type TypeClassType = ContravariantMonoidal[F] + + val self = fa + val typeClassInstance = F + } +} +abstract class ContravariantMonoidalOps[F[_], A] extends ContravariantMonoidal.Ops[F, A] diff --git a/core/src/main/scala/cats/syntax/contravariantSemigroupal.scala b/core/src/main/scala/cats/syntax/contravariantSemigroupal.scala new file mode 100644 index 0000000000..1c583d1467 --- /dev/null +++ b/core/src/main/scala/cats/syntax/contravariantSemigroupal.scala @@ -0,0 +1,14 @@ +package cats +package syntax + +import cats.ContravariantSemigroupal + +trait ContravariantSemigroupalSyntax extends TupleSemigroupalSyntax { + implicit final def catsSyntaxContravariantSemigroupal[F[_], A](fa: F[A])(implicit F: ContravariantSemigroupal[F]): ContravariantSemigroupal.Ops[F, A] = + new ContravariantSemigroupal.Ops[F, A] { + type TypeClassType = ContravariantSemigroupal[F] + + val self = fa + val typeClassInstance = F + } +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index cdf41ebc65..dd1cceb060 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -17,6 +17,8 @@ package object syntax { object comonad extends ComonadSyntax object compose extends ComposeSyntax object contravariant extends ContravariantSyntax + object contravariantSemigroupal extends ContravariantSemigroupalSyntax + object contravariantMonoidal extends ContravariantMonoidalSyntax object either extends EitherSyntax object eq extends EqSyntax object flatMap extends FlatMapSyntax diff --git a/docs/src/main/resources/microsite/data/menu.yml b/docs/src/main/resources/microsite/data/menu.yml index 2ef6321489..c3ef18d054 100644 --- a/docs/src/main/resources/microsite/data/menu.yml +++ b/docs/src/main/resources/microsite/data/menu.yml @@ -69,6 +69,9 @@ options: - title: Contravariant url: typeclasses/contravariant.html menu_section: variance + - title: ContravariantMonoidal + url: typeclasses/contravariantmonoidal.html + menu_type: typeclasses - title: Invariant url: typeclasses/invariant.html menu_section: variance @@ -76,6 +79,7 @@ options: url: typeclasses/invariantmonoidal.html menu_section: variance + - title: Eq url: typeclasses/eq.html menu_type: typeclasses diff --git a/docs/src/main/tut/typeclasses/contravariantmonoidal.md b/docs/src/main/tut/typeclasses/contravariantmonoidal.md new file mode 100644 index 0000000000..ddcd56cfa1 --- /dev/null +++ b/docs/src/main/tut/typeclasses/contravariantmonoidal.md @@ -0,0 +1,110 @@ +--- +layout: docs +title: "Contravariant Monoidal" +section: "typeclasses" +source: "core/src/main/scala/cats/ContravariantMonoidal.scala" +scaladoc: "#cats.ContravariantMonoidal" +--- +# Contravariant Monoidal + +The `ContravariantMonoidal` type class is for [`Contravariant`](contravariant.html) functors that can define a +`product` function and a `unit` function. + +```tut:silent +import cats.Contravariant + +trait ContravariantMonoidal[F[_]] extends Contravariant[F] { + def unit[A]: F[A] + + def product[A, B](fa: F[A], fc: F[B]): F[(A, B)] + + def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] = + contramap(product(fb, fc))(f) +} +``` + +Notice that this allows us to define the `contramap2` function, much like +the `map2` function and the `pure` function on the `Applicative` typeclass, but in reverse. + +Basically, if you have two contexts `F[B]` and `F[C]` for types +`B` and `C`, as well as a way to produce types `B` and `C` simultaneously +from a type `A`, then `ContravariantMonoidal` allows you to obtain +a context `F[A]` for the type `A`. + +Examples of `ContravariantMonoidal` instances are [`Eq`](eq.html) and [`Const`](../datatypes/const.html), +but there are also interesting instances for other types. + +## Predicates Have `ContravariantMonoidal` + +An example application would be the case of predicates. Consider the type, + +```tut:book:silent:reset +import cats._ + +import cats.implicits._ + +case class Predicate[A](run: A => Boolean) +``` + +Then, we can exhibit a `ContravariantMonoidal` for `Predicate` by basing it on the +`Monoid` for `Boolean` via `&&` as, + +```tut:book:silent +implicit val contravariantMonoidalPredicate: ContravariantMonoidal[Predicate] = + new ContravariantMonoidal [Predicate] { + def unit[A]: Predicate[A] = Predicate[A](Function.const(true)) + + def product[A, B](fa: Predicate[A], fb: Predicate[B]): Predicate[(A, B)] = + Predicate(x => fa.run(x._1) && fb.run(x._2)) + + def contramap[A, B](fa: Predicate[A])(f: B => A): Predicate[B] = + Predicate(x => fa.run(f(x))) + } +``` + +We could have also used `false` and `||`, but the "and" version +tends to be a little more convenient for this application. + +Just like for `Contravariant`, we can `contramap` to +pull `Predicates` back along functions. + +```tut:book +case class Money(value: Long) +def isEven: Predicate[Long] = Predicate(_ % 2 == 0) + +def isEvenMoney: Predicate[Money] = isEven.contramap(_.value) + +isEvenMoney.run(Money(55)) +``` + +We can also lift functions contravariantly into +the context instead of contramapping repeatedly. + +```tut:book +def times2Predicate: Predicate[Long] => Predicate[Long] = + ContravariantMonoidal[Predicate].liftContravariant((x: Long) => 2*x) + +def liftMoney: Predicate[Long] => Predicate[Money] = + ContravariantMonoidal[Predicate].liftContravariant(_.value) + +def trivial = times2Predicate(isEven) +trivial.run(2) +trivial.run(5) +``` + +More interestingly, we can combine multiple predicates using +a `contramapN`. + +```tut:book +case class Transaction(value: Money, payee: String) + +def isEvan: Predicate[String] = Predicate(_ == "Evan") + +def isGreaterThan50Dollars: Predicate[Money] = liftMoney(Predicate(_ > 50)) + +def isEvenPaymentToEvanOfMoreThan50 = + (isEvenMoney, isGreaterThan50Dollars, isEvan).contramapN( + (trans: Transaction) => (trans.value, trans.value, trans.payee)) + +isEvenPaymentToEvanOfMoreThan50.run(Transaction(Money(56), "Evan")) +``` diff --git a/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala b/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala new file mode 100644 index 0000000000..07a1cb24c6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala @@ -0,0 +1,30 @@ +package cats +package laws + +import cats.ContravariantMonoidal +import cats.syntax.contravariant._ +import cats.syntax.contravariantSemigroupal._ + +/** + * Laws that must hold for any `cats.ContravariantMonoidal`. + */ +trait ContravariantMonoidalLaws[F[_]] extends ContravariantSemigroupalLaws[F] { + implicit override def F: ContravariantMonoidal[F] + + def contravariantMonoidalUnitRight[A](fa: F[A]): IsEq[F[A]] = + (fa, F.unit[A]).contramapN(delta[A]) <-> fa + + def contravariantMonoidalUnitLeft[A](fa: F[A]): IsEq[F[A]] = + (F.unit[A], fa).contramapN(delta[A]) <-> fa + + def contravariantMonoidalContramap2CompatibleContramapLeft[A, B, C](fa: F[A], f: B => (A, C)): IsEq[F[B]] = + (fa, F.unit[C]).contramapN(f) <-> fa.contramap(f andThen (_._1)) + + def contravariantMonoidalContramap2CompatibleContramapRight[A, B, C](fa: F[A], f: C => (B, A)): IsEq[F[C]] = + (F.unit[B], fa).contramapN(f) <-> fa.contramap(f andThen (_._2)) +} + +object ContravariantMonoidalLaws { + def apply[F[_]](implicit ev: ContravariantMonoidal[F]): ContravariantMonoidalLaws[F] = + new ContravariantMonoidalLaws[F] { def F: ContravariantMonoidal[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/ContravariantSemigroupalLaws.scala b/laws/src/main/scala/cats/laws/ContravariantSemigroupalLaws.scala new file mode 100644 index 0000000000..4ba3a6990d --- /dev/null +++ b/laws/src/main/scala/cats/laws/ContravariantSemigroupalLaws.scala @@ -0,0 +1,21 @@ +package cats +package laws + +import cats.ContravariantSemigroupal +import cats.syntax.contravariantSemigroupal._ + +/** + * Laws that are expected for any `cats.ContravariantSemigroupal`. + */ +trait ContravariantSemigroupalLaws[F[_]] extends ContravariantLaws[F] with SemigroupalLaws[F] { + implicit override def F: ContravariantSemigroupal[F] + + def delta[A](a: A): (A, A) = (a, a) + + def contravariantSemigroupalContramap2DiagonalAssociates[A](m: F[A], n: F[A], o: F[A]): IsEq[F[A]] = + ((m, n).contramapN(delta[A]), o).contramapN(delta[A]) <-> (m, (n, o).contramapN(delta[A])).contramapN(delta[A]) +} +object ContravariantSemigroupalLaws { + def apply[F[_]](implicit ev: ContravariantSemigroupal[F]): ContravariantSemigroupalLaws[F] = + new ContravariantSemigroupalLaws[F] { def F: ContravariantSemigroupal[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ContravariantMonoidalTests.scala b/laws/src/main/scala/cats/laws/discipline/ContravariantMonoidalTests.scala new file mode 100644 index 0000000000..21b30dc860 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ContravariantMonoidalTests.scala @@ -0,0 +1,47 @@ +package cats +package laws +package discipline + +import cats.ContravariantMonoidal +import cats.laws.discipline.SemigroupalTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen} +import org.scalacheck.Prop._ + +trait ContravariantMonoidalTests[F[_]] extends ContravariantSemigroupalTests[F] { + def laws: ContravariantMonoidalLaws[F] + + def contravariantMonoidal[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit + arbFA: Arbitrary[F[A]], + arbFB: Arbitrary[F[B]], + arbFC: Arbitrary[F[C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + val name = "contravariantMonoidal" + val parents = Seq(contravariantSemigroupal[A, B, C]) + val bases = Seq.empty + val props = Seq( + "contravariantMonoidal right unit" -> + forAll(laws.contravariantMonoidalUnitRight[A] _), + "contravariantMonoidal left unit" -> + forAll(laws.contravariantMonoidalUnitLeft[A] _), + "contravariantMonoidal contramap2 compatible contramap left" -> + forAll(laws.contravariantMonoidalContramap2CompatibleContramapLeft[A, B, C] _), + "contravariantMonoidal contramap2 compatible contramap right" -> + forAll(laws.contravariantMonoidalContramap2CompatibleContramapRight[A, B, C] _) + ) + } + } +} + +object ContravariantMonoidalTests { + def apply[F[_]: ContravariantMonoidal]: ContravariantMonoidalTests[F] = + new ContravariantMonoidalTests[F] { def laws: ContravariantMonoidalLaws[F] = ContravariantMonoidalLaws[F] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ContravariantSemigroupalTests.scala b/laws/src/main/scala/cats/laws/discipline/ContravariantSemigroupalTests.scala new file mode 100644 index 0000000000..581fd34225 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ContravariantSemigroupalTests.scala @@ -0,0 +1,41 @@ +package cats +package laws +package discipline + +import cats.ContravariantSemigroupal +import cats.laws.discipline.SemigroupalTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen} +import org.scalacheck.Prop._ + +trait ContravariantSemigroupalTests[F[_]] extends ContravariantTests[F] with SemigroupalTests[F] { + def laws: ContravariantSemigroupalLaws[F] + + def contravariantSemigroupal[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit + arbFA: Arbitrary[F[A]], + arbFB: Arbitrary[F[B]], + arbFC: Arbitrary[F[C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + val name = "contravariantSemigroupal" + val parents = Seq(contravariant[A, B, C], semigroupal[A, B, C]) + val bases = Seq.empty + val props = Seq( + "contravariantSemigroupal contramap2 delta associates" -> + forAll(laws.contravariantSemigroupalContramap2DiagonalAssociates[A] _) + ) + } + } +} + +object ContravariantSemigroupalTests { + def apply[F[_]: ContravariantSemigroupal]: ContravariantSemigroupalTests[F] = + new ContravariantSemigroupalTests[F] { def laws: ContravariantSemigroupalLaws[F] = ContravariantSemigroupalLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/ConstSuite.scala b/tests/src/test/scala/cats/tests/ConstSuite.scala index 6be8244d75..116e4f8e59 100644 --- a/tests/src/test/scala/cats/tests/ConstSuite.scala +++ b/tests/src/test/scala/cats/tests/ConstSuite.scala @@ -45,6 +45,9 @@ class ConstSuite extends CatsSuite { checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int]) checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]])) + checkAll("Const[String, Int]", ContravariantMonoidalTests[Const[String, ?]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[Const[String, ?]]", SerializableTests.serializable(ContravariantMonoidal[Const[String, ?]])) + checkAll("Const[?, ?]", BifoldableTests[Const].bifoldable[Int, Int, Int]) checkAll("Bifoldable[Const]", SerializableTests.serializable(Bifoldable[Const])) diff --git a/tests/src/test/scala/cats/tests/EqSuite.scala b/tests/src/test/scala/cats/tests/EqSuite.scala index 4b2efeaf73..56fea4358c 100644 --- a/tests/src/test/scala/cats/tests/EqSuite.scala +++ b/tests/src/test/scala/cats/tests/EqSuite.scala @@ -12,6 +12,7 @@ class EqSuite extends FunSuite { Contravariant[Eq] Semigroupal[Eq] ContravariantSemigroupal[Eq] + ContravariantMonoidal[Eq] } { @@ -20,5 +21,6 @@ class EqSuite extends FunSuite { Contravariant[Eq] Semigroupal[Eq] ContravariantSemigroupal[Eq] + ContravariantMonoidal[Eq] } } diff --git a/tests/src/test/scala/cats/tests/EquivSuite.scala b/tests/src/test/scala/cats/tests/EquivSuite.scala index 26fb8271a6..86c8e0ec31 100644 --- a/tests/src/test/scala/cats/tests/EquivSuite.scala +++ b/tests/src/test/scala/cats/tests/EquivSuite.scala @@ -12,8 +12,12 @@ class EquivSuite extends CatsSuite { Contravariant[Equiv] Semigroupal[Equiv] ContravariantSemigroupal[Equiv] + ContravariantMonoidal[Equiv] checkAll("Contravariant[Equiv]", ContravariantTests[Equiv].contravariant[Int, Int, Int]) checkAll("Semigroupal[Equiv]", SemigroupalTests[Equiv].semigroupal[Int, Int, Int]) - checkAll("Contravariant[Equiv]", SerializableTests.serializable(Contravariant[Equiv])) + checkAll("ContravariantMonoidal[Equiv]", + ContravariantMonoidalTests[Equiv].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[Equiv]", + SerializableTests.serializable(ContravariantMonoidal[Equiv])) } diff --git a/tests/src/test/scala/cats/tests/FunctionSuite.scala b/tests/src/test/scala/cats/tests/FunctionSuite.scala index 8b26aeb23d..215b696b62 100644 --- a/tests/src/test/scala/cats/tests/FunctionSuite.scala +++ b/tests/src/test/scala/cats/tests/FunctionSuite.scala @@ -90,7 +90,6 @@ class FunctionSuite extends CatsSuite { checkAll("Group[() => Grp]", SerializableTests.serializable(Group[() => Grp])) checkAll("CommutativeGroup[() => CGrp]", SerializableTests.serializable(CommutativeGroup[() => CGrp])) - // law checks for the various Function1-related instances checkAll("Function1[String, Semi]", SemigroupTests[Function1[String, Semi]].semigroup) checkAll("Function1[String, CSemi]", CommutativeSemigroupTests[Function1[String, CSemi]].commutativeSemigroup) @@ -101,6 +100,9 @@ class FunctionSuite extends CatsSuite { checkAll("Function1[String, CMono]", CommutativeMonoidTests[Function1[String, CMono]].commutativeMonoid) checkAll("Function1[String, Grp]", GroupTests[Function1[String, Grp]].group) checkAll("Function1[String, CGrp]", CommutativeGroupTests[Function1[String, CGrp]].commutativeGroup) + // Isos for ContravariantMonoidal + implicit val isoCodomain = SemigroupalTests.Isomorphisms.invariant[Function1[?, Long]] + checkAll("Function1[?, Monoid]", ContravariantMonoidalTests[Function1[?, Long]].contravariantMonoidal[Int, Int, Int]) // serialization tests for the various Function1-related instances checkAll("Semigroup[String => Semi]", SerializableTests.serializable(Semigroup[String => Semi])) @@ -112,4 +114,5 @@ class FunctionSuite extends CatsSuite { checkAll("CommutativeMonoid[String => CMono]", SerializableTests.serializable(CommutativeMonoid[String => CMono])) checkAll("Group[String => Grp]", SerializableTests.serializable(Group[String => Grp])) checkAll("CommutativeGroup[String => CGrp]", SerializableTests.serializable(CommutativeGroup[String => CGrp])) + checkAll("ContravariantMonoidal[Function1[?, Monoid]]", SerializableTests.serializable(ContravariantMonoidal[? => Long])) } diff --git a/tests/src/test/scala/cats/tests/IdTSuite.scala b/tests/src/test/scala/cats/tests/IdTSuite.scala index 7cc192890c..770d753599 100644 --- a/tests/src/test/scala/cats/tests/IdTSuite.scala +++ b/tests/src/test/scala/cats/tests/IdTSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{IdT, NonEmptyList} +import cats.data.{Const, IdT, NonEmptyList} import cats.kernel.laws.discipline.{OrderTests, EqTests} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -45,6 +45,13 @@ class IdTSuite extends CatsSuite { checkAll("Applicative[IdT[ListWrapper, ?]]", SerializableTests.serializable(Applicative[IdT[ListWrapper, ?]])) } + { + checkAll("IdT[Const[String, ?], ?]", + ContravariantMonoidalTests[IdT[Const[String, ?], ?]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[IdT[Const[String, ?], ?]]", + SerializableTests.serializable(ContravariantMonoidal[IdT[Const[String, ?], ?]])) + } + { implicit val F = ListWrapper.flatMap diff --git a/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala b/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala index 1f5fe0091f..1c5be6d9c6 100644 --- a/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala +++ b/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala @@ -2,7 +2,7 @@ package cats package tests import cats.arrow.{Profunctor, Strong} -import cats.data.{EitherT, IndexedStateT, State, StateT} +import cats.data.{Const, EitherT, IndexedStateT, State, StateT} import cats.arrow.Profunctor import cats.kernel.instances.tuple._ @@ -372,6 +372,13 @@ class IndexedStateTSuite extends CatsSuite { SemigroupK[IndexedStateT[ListWrapper, Int, Int, ?]] } + { + // F has a ContravariantMonoidal + val SD = ContravariantMonoidal[StateT[Const[String, ?], String, ?]] + + checkAll("ContravariantMonoidal[StateT[Const[String, ?], String, ?]]", SerializableTests.serializable(SD)) + } + { implicit val iso = SemigroupalTests.Isomorphisms.invariant[State[Long, ?]] diff --git a/tests/src/test/scala/cats/tests/KleisliSuite.scala b/tests/src/test/scala/cats/tests/KleisliSuite.scala index 4f7257288f..d9d8074b8f 100644 --- a/tests/src/test/scala/cats/tests/KleisliSuite.scala +++ b/tests/src/test/scala/cats/tests/KleisliSuite.scala @@ -3,7 +3,7 @@ package tests import cats.Contravariant import cats.arrow._ -import cats.data.{EitherT, Kleisli, Reader} +import cats.data.{Const, EitherT, Kleisli, Reader} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ @@ -87,6 +87,14 @@ class KleisliSuite extends CatsSuite { checkAll("Alternative[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Alternative[Kleisli[Option, Int, ?]])) } + { + implicit val catsDataContravariantMonoidalForKleisli = Kleisli.catsDataContravariantMonoidalForKleisli[Const[String, ?], Int] + checkAll("Kleisli[Const[String, ?], Int, Int]", + ContravariantMonoidalTests[Kleisli[Const[String, ?], Int, ?]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[Kleisli[Option, Int, ?]]", + SerializableTests.serializable(ContravariantMonoidal[Kleisli[Const[String, ?], Int, ?]])) + } + { implicit val catsDataApplicativeForKleisli = Kleisli.catsDataApplicativeForKleisli[Option, Int] checkAll("Kleisli[Option, Int, Int]", ApplicativeTests[Kleisli[Option, Int, ?]].applicative[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/NestedSuite.scala b/tests/src/test/scala/cats/tests/NestedSuite.scala index 8ad49c23f7..616022d671 100644 --- a/tests/src/test/scala/cats/tests/NestedSuite.scala +++ b/tests/src/test/scala/cats/tests/NestedSuite.scala @@ -50,6 +50,14 @@ class NestedSuite extends CatsSuite { checkAll("Contravariant[Nested[Option, Show, ?]]", SerializableTests.serializable(Contravariant[Nested[Option, Show, ?]])) } + { + // Applicative + ContravariantMonoidal functor composition + checkAll("Nested[Option, Const[String, ?], ?]", + ContravariantMonoidalTests[Nested[Option, Const[String, ?], ?]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[Nested[Option, Const[String, ?], ?]", + SerializableTests.serializable(ContravariantMonoidal[Nested[Option, Const[String, ?], ?]])) + } + { // Contravariant + Contravariant = Functor type ConstInt[A] = Const[Int, A] diff --git a/tests/src/test/scala/cats/tests/OptionTSuite.scala b/tests/src/test/scala/cats/tests/OptionTSuite.scala index 047240a8cc..d3b7eea447 100644 --- a/tests/src/test/scala/cats/tests/OptionTSuite.scala +++ b/tests/src/test/scala/cats/tests/OptionTSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.OptionT +import cats.data.{Const, OptionT} import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests, OrderTests, PartialOrderTests, EqTests} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -43,6 +43,14 @@ class OptionTSuite extends CatsSuite { checkAll("Functor[OptionT[ListWrapper, ?]]", SerializableTests.serializable(Functor[OptionT[ListWrapper, ?]])) } + + { + // F has a ContravariantMonoidal + checkAll("OptionT[Const[String, ?], Int]", ContravariantMonoidalTests[OptionT[Const[String, ?], ?]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[OptionT[Const[String, ?], Int]]", + SerializableTests.serializable(ContravariantMonoidal[OptionT[Const[String, ?], ?]])) + } + { // F has a Monad implicit val F = ListWrapper.monad diff --git a/tests/src/test/scala/cats/tests/OrderSuite.scala b/tests/src/test/scala/cats/tests/OrderSuite.scala index 304cc5e59f..adb5857d78 100644 --- a/tests/src/test/scala/cats/tests/OrderSuite.scala +++ b/tests/src/test/scala/cats/tests/OrderSuite.scala @@ -8,6 +8,7 @@ class OrderSuite extends CatsSuite { { Invariant[Order] Contravariant[Order] + ContravariantMonoidal[Order] } checkAll("Int", OrderTests[Int].order) @@ -21,6 +22,7 @@ object OrderSuite { import cats.instances.order._ Invariant[Order] Contravariant[Order] + ContravariantMonoidal[Order] () } diff --git a/tests/src/test/scala/cats/tests/OrderingSuite.scala b/tests/src/test/scala/cats/tests/OrderingSuite.scala index 2af60c5992..42e8323de5 100644 --- a/tests/src/test/scala/cats/tests/OrderingSuite.scala +++ b/tests/src/test/scala/cats/tests/OrderingSuite.scala @@ -12,8 +12,12 @@ class OrderingSuite extends CatsSuite { Contravariant[Ordering] Semigroupal[Ordering] ContravariantSemigroupal[Ordering] + ContravariantMonoidal[Ordering] checkAll("Contravariant[Ordering]", ContravariantTests[Ordering].contravariant[Int, Int, Int]) checkAll("Semigroupal[Ordering]", SemigroupalTests[Ordering].semigroupal[Int, Int, Int]) - checkAll("Contravariant[Ordering]", SerializableTests.serializable(Contravariant[Ordering])) + checkAll("ContravariantMonoidal[Ordering]", + ContravariantMonoidalTests[Ordering].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[Ordering]", + SerializableTests.serializable(ContravariantMonoidal[Ordering])) } diff --git a/tests/src/test/scala/cats/tests/Tuple2KSuite.scala b/tests/src/test/scala/cats/tests/Tuple2KSuite.scala index 1795218548..822de76984 100644 --- a/tests/src/test/scala/cats/tests/Tuple2KSuite.scala +++ b/tests/src/test/scala/cats/tests/Tuple2KSuite.scala @@ -2,7 +2,7 @@ package cats package tests -import cats.data.{Tuple2K, Validated} +import cats.data.{Const, Tuple2K, Validated} import cats.Contravariant import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -20,6 +20,11 @@ class Tuple2KSuite extends CatsSuite { checkAll("Tuple2K[Show, Order, Int]", ContravariantTests[λ[α => Tuple2K[Show, Order, α]]].contravariant[Int, Int, Int]) checkAll("Contravariant[Tuple2K[Show, Order, Int]]", SerializableTests.serializable(Contravariant[λ[α => Tuple2K[Show, Order, α]]])) + checkAll("Tuple2K[Const[String, ?], Const[Int, ?], Int]", + ContravariantMonoidalTests[λ[α => Tuple2K[Const[String, ?], Const[Int, ?], α]]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[Tuple2K[Const[String, ?], Const[Int, ?], Int]]", + SerializableTests.serializable(ContravariantMonoidal[λ[α => Tuple2K[Const[String, ?], Const[Int, ?], α]]])) + checkAll("Show[Tuple2K[Option, Option, Int]]", SerializableTests.serializable(Show[Tuple2K[Option, Option, Int]])) { diff --git a/tests/src/test/scala/cats/tests/WriterTSuite.scala b/tests/src/test/scala/cats/tests/WriterTSuite.scala index 06ca448ed7..f1e0329da2 100644 --- a/tests/src/test/scala/cats/tests/WriterTSuite.scala +++ b/tests/src/test/scala/cats/tests/WriterTSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{EitherT, Validated, Writer, WriterT} +import cats.data.{Const, EitherT, Validated, Writer, WriterT} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -362,6 +362,14 @@ class WriterTSuite extends CatsSuite { checkAll("MonadError[WriterT[Option, ListWrapper[Int], ?], Unit]", SerializableTests.serializable(MonadError[WriterT[Option, ListWrapper[Int], ?], Unit])) } + { + // F has a ContravariantMonoidal + ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]] + + checkAll("WriterT[Const[String, ?], Int, ?]", ContravariantMonoidalTests[WriterT[Const[String, ?], Int, ?]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]]", SerializableTests.serializable(ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]])) + } + checkAll("WriterT[Option, Int, ?]", CommutativeMonadTests[WriterT[Option, Int, ?]].commutativeMonad[Int, Int, Int]) checkAll("CommutativeMonad[WriterT[Option, Int, ?]]",SerializableTests.serializable(CommutativeMonad[WriterT[Option, Int, ?]])) }