From 0501b2c40d49050d5d73df9356e3ffb3eab893f1 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Mon, 30 Oct 2017 21:01:13 -0700 Subject: [PATCH 01/14] Add Divisible Adds Divisible typeclass Adds laws for Divisible and tests for relevant instances Adds documentation for Divisible typeclass Adds instances for: - Const - Nested - Tuple2K - Function with Monoid codomain - ReaderT - Kleisli - IdT - Eq - Equiv - Order - Ordering - OptionT - WriterT - IndexedStateT Adds tests --- core/src/main/scala/cats/Composed.scala | 10 +++ core/src/main/scala/cats/Divisible.scala | 52 ++++++++++++ core/src/main/scala/cats/data/Const.scala | 6 ++ core/src/main/scala/cats/data/IdT.scala | 16 +++- .../main/scala/cats/data/IndexedStateT.scala | 20 +++++ core/src/main/scala/cats/data/Kleisli.scala | 12 +++ core/src/main/scala/cats/data/Nested.scala | 14 ++++ core/src/main/scala/cats/data/OptionT.scala | 13 +++ core/src/main/scala/cats/data/Tuple2K.scala | 13 +++ core/src/main/scala/cats/data/WriterT.scala | 16 ++++ core/src/main/scala/cats/instances/eq.scala | 24 ++++-- .../src/main/scala/cats/instances/equiv.scala | 31 ++++--- .../main/scala/cats/instances/function.scala | 9 ++ .../src/main/scala/cats/instances/order.scala | 26 +++--- .../main/scala/cats/instances/ordering.scala | 25 +++--- core/src/main/scala/cats/syntax/all.scala | 1 + .../main/scala/cats/syntax/divisible.scala | 6 ++ core/src/main/scala/cats/syntax/package.scala | 1 + .../main/resources/microsite/data/menu.yml | 4 + docs/src/main/tut/typeclasses/divisible.md | 83 +++++++++++++++++++ .../main/scala/cats/laws/DivisibleLaws.scala | 38 +++++++++ .../cats/laws/discipline/DivisibleTests.scala | 36 ++++++++ .../test/scala/cats/tests/ConstSuite.scala | 3 + tests/src/test/scala/cats/tests/EqSuite.scala | 2 + .../test/scala/cats/tests/EquivSuite.scala | 4 +- .../test/scala/cats/tests/FunctionSuite.scala | 2 + .../src/test/scala/cats/tests/IdTSuite.scala | 7 +- .../scala/cats/tests/IndexedStateTSuite.scala | 9 +- .../test/scala/cats/tests/KleisliSuite.scala | 8 +- .../test/scala/cats/tests/NestedSuite.scala | 6 ++ .../test/scala/cats/tests/OptionTSuite.scala | 9 +- .../test/scala/cats/tests/OrderSuite.scala | 2 + .../test/scala/cats/tests/OrderingSuite.scala | 4 +- .../test/scala/cats/tests/Tuple2KSuite.scala | 5 +- .../test/scala/cats/tests/WriterTSuite.scala | 7 +- 35 files changed, 478 insertions(+), 46 deletions(-) create mode 100644 core/src/main/scala/cats/Divisible.scala create mode 100644 core/src/main/scala/cats/syntax/divisible.scala create mode 100644 docs/src/main/tut/typeclasses/divisible.md create mode 100644 laws/src/main/scala/cats/laws/DivisibleLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/DivisibleTests.scala diff --git a/core/src/main/scala/cats/Composed.scala b/core/src/main/scala/cats/Composed.scala index 6ed0c8a19d..1cb67a4a57 100644 --- a/core/src/main/scala/cats/Composed.scala +++ b/core/src/main/scala/cats/Composed.scala @@ -113,6 +113,16 @@ private[cats] trait ComposedContravariantCovariant[F[_], G[_]] extends Contravar F.contramap(fga)(gb => G.map(gb)(f)) } +private[cats] trait ComposedApplicativeDivisible[F[_], G[_]] extends Divisible[λ[α => F[G[α]]]] { outer => + def F: Applicative[F] + def G: Divisible[G] + + override def unit[A]: F[G[A]] = F.pure(G.unit) + + override def contramap2[A, B, C](fb: F[G[B]], fc: F[G[C]])(f: A => (B, C)): F[G[A]] = + F.ap2(F.pure((gb: G[B], gc: G[C]) => G.contramap2(gb, gc)(f)))(fb, fc) +} + 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/Divisible.scala b/core/src/main/scala/cats/Divisible.scala new file mode 100644 index 0000000000..72080cd4b0 --- /dev/null +++ b/core/src/main/scala/cats/Divisible.scala @@ -0,0 +1,52 @@ +package cats + +import simulacrum.typeclass + +/** + * Divisible functors + * + * Must obey the laws defined in cats.laws.DivisibleLaws. + * + * Based on ekmett's contravariant library: + * https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html + */ +@typeclass trait Divisible[F[_]] extends ContravariantSemigroupal[F] { self => + + /** + * `unit` produces an instance of `F` for any type `A` + */ + def unit[A]: F[A] + + /** + * `contramap2` + * + * Given two values in the Divisible context, and a function producing a value of both types, + * yields an element of the domain of the function lifted into the context. + */ + def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] + + + // Technically, this is not correct, as the Applicative is composed with the Divisible, not the other way around + def composeApplicative[G[_]: Applicative]: Divisible[λ[α => G[F[α]]]] = + new ComposedApplicativeDivisible[G, F] { + val F = Applicative[G] + val G = self + } + + /** + * Allows two instances to packaged into an instance over the product + */ + override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = + contramap2(fa, fb)(identity) + + /** + * Lifts a function into the Divisible contravariantly + */ + def liftD[A, B](f: A => B): F[B] => F[A] = + contramap2(unit, _: F[B])(((b: B) => (b, b)) compose f) + + + override def contramap[A, B](fa: F[A])(f: (B) => A): F[B] = + liftD(f)(fa) +} + diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 147a2a3fe9..29f311e5c4 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -70,6 +70,12 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { fa.retag[B] } + implicit def catsDataDivisibleForConst[D: Monoid]: Divisible[Const[D, ?]] = new Divisible[Const[D, ?]] { + override def unit[A] = Const.empty[D, A] + override def contramap2[A, B, C](fb: Const[D, B], fc: Const[D, C])(f: A => (B, C)): Const[D, A] = + fb.retag[A] combine fc.retag[A] + } + 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 diff --git a/core/src/main/scala/cats/data/IdT.scala b/core/src/main/scala/cats/data/IdT.scala index c2fb5a4cce..1407da7da2 100644 --- a/core/src/main/scala/cats/data/IdT.scala +++ b/core/src/main/scala/cats/data/IdT.scala @@ -73,6 +73,15 @@ 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 IdTDivisible[F[_]] extends Divisible[IdT[F, ?]] { + implicit val F0: Divisible[F] + + override def unit[A]: IdT[F, A] = IdT(F0.unit[A]) + + override def contramap2[A, B, C](fb: IdT[F, B], fc: IdT[F, C])(f: A => (B, C)): IdT[F, A] = + IdT(F0.contramap2(fb.value, fc.value)(f)) +} + private[data] sealed trait IdTFlatMap[F[_]] extends FlatMap[IdT[F, ?]] with IdTApply[F] { implicit val F0: FlatMap[F] @@ -123,7 +132,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 catsDataDivisibleForIdT[F[_]](implicit F: Divisible[F]): Divisible[IdT[F, ?]] = + new IdTDivisible[F] { implicit val F0: Divisible[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..c985ec431a 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 catsDataDivisibleForIndexedStateT[F[_], S](implicit FD: Divisible[F], + FA: Applicative[F]): Divisible[IndexedStateT[F, S, S, ?]] = + new IndexedStateTDivisible[F, S] { implicit def F = FD; implicit def G = FA } } private[data] sealed abstract class IndexedStateTInstances1 extends IndexedStateTInstances2 { @@ -362,6 +366,22 @@ 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 IndexedStateTDivisible[F[_], S] extends Divisible[IndexedStateT[F, S, S, ?]]{ + implicit def F: Divisible[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 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) => + F.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))) + }))) +} + 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..f8ff68fafb 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -165,6 +165,9 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 { implicit def catsDataAlternativeForKleisli[F[_], A](implicit F0: Alternative[F]): Alternative[Kleisli[F, A, ?]] = new KleisliAlternative[F, A] { def F: Alternative[F] = F0 } + + implicit def catsDataDivisibleForKleisli[F[_], A](implicit F0: Divisible[F]): Divisible[Kleisli[F, A, ?]] = + new KleisliDivisible[F, A] { def F: Divisible[F] = F0 } } private[data] sealed abstract class KleisliInstances3 extends KleisliInstances4 { @@ -294,6 +297,15 @@ private[data] trait KleisliAlternative[F[_], A] extends Alternative[Kleisli[F, A implicit def F: Alternative[F] } +private[data] sealed trait KleisliDivisible[F[_], D] extends Divisible[Kleisli[F, D, ?]] { + implicit def F: Divisible[F] + + override def unit[A]: Kleisli[F, D, A] = Kleisli(Function.const(F.unit[A])) + + override def contramap2[A, B, C](fb: Kleisli[F, D, B], fc: Kleisli[F, D, C])(f: A => (B, C)): Kleisli[F, D, A] = + Kleisli(d => F.contramap2(fb.run(d), fc.run(d))(f)) +} + 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..c7c8c74201 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -62,6 +62,11 @@ private[data] sealed abstract class NestedInstances1 extends NestedInstances2 { new NestedFunctor[F, G] { val FG: Functor[λ[α => F[G[α]]]] = Contravariant[F].compose[G] } + + implicit def catsDataDivisibleForApplicativeForNested[F[_]: Applicative, G[_]: Divisible]: Divisible[Nested[F, G, ?]] = + new NestedDivisible[F, G] { + val FG: Divisible[λ[α => F[G[α]]]] = Divisible[G].composeApplicative[F] + } } private[data] sealed abstract class NestedInstances2 extends NestedInstances3 { @@ -263,3 +268,12 @@ 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 NestedDivisible[F[_], G[_]] extends Divisible[Nested[F, G, ?]] { + def FG: Divisible[λ[α => F[G[α]]]] + + def unit[A]: Nested[F, G, A] = Nested(FG.unit) + + def contramap2[A, B, C](fb: Nested[F, G, B], fc: Nested[F, G, C])(f: A => (B, C)): Nested[F, G, A] = + Nested(FG.contramap2(fb.value, fc.value)(f)) +} diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index cf59218c8f..c7a4e12f99 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 catsDataDivisibleForOptionT[F[_]](implicit F0: Divisible[F]): Divisible[OptionT[F, ?]] = + new OptionTDivisible[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,16 @@ private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] wi OptionT(F.handleErrorWith(fa.value)(f(_).value)) } +private trait OptionTDivisible[F[_]] extends Divisible[OptionT[F, ?]] { + def F: Divisible[F] + + override def unit[A]: OptionT[F, A] = OptionT (F.unit) + + // Really this wants to be written in terms of arrow :/ + override def contramap2[A, B, C](fb: OptionT[F, B], fc: OptionT[F, C])(f: A => (B, C)): OptionT[F, A] = + OptionT(F.contramap2(fb.value, fc.value)(x => (x.map(f andThen (_._1)), x.map(f andThen (_._2))))) +} + 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..e966c52a2c 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -33,6 +33,11 @@ private[data] sealed abstract class Tuple2KInstances extends Tuple2KInstances0 { def F: Contravariant[F] = FC def G: Contravariant[G] = GC } + implicit def catsDataDivisibleForTuple2k[F[_], G[_]](implicit FD: Divisible[F], GD: Divisible[G]): Divisible[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KDivisible[F, G] { + def F: Divisible[F] = FD + def G: Divisible[G] = GD + } } private[data] sealed abstract class Tuple2KInstances0 extends Tuple2KInstances1 { @@ -123,6 +128,14 @@ 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 Tuple2KDivisible[F[_], G[_]] extends Divisible[λ[α => Tuple2K[F, G, α]]] { + def F: Divisible[F] + def G: Divisible[G] + def unit[A]: Tuple2K[F, G, A] = Tuple2K(F.unit, G.unit) + def contramap2[A, B, C](fb: Tuple2K[F, G, B], fc: Tuple2K[F, G, C])(f: A => (B, C)): Tuple2K[F, G, A] = + Tuple2K(F.contramap2(fb.first, fc.first)(f), G.contramap2(fb.second, fc.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..4da02344ef 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 catsDataDivisibleForWriterT[F[_], L](implicit F: Divisible[F]): Divisible[WriterT[F, L, ?]] = + new WriterTDivisible[F, L] { + implicit val F0: Divisible[F] = F + } } private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 { @@ -353,6 +358,17 @@ private[data] sealed trait WriterTAlternative[F[_], L] extends Alternative[Write override implicit def F0: Alternative[F] } +private[data] sealed trait WriterTDivisible[F[_], L] extends Divisible[WriterT[F, L, ?]] { + implicit def F0: Divisible[F] + + override def unit[A]: WriterT[F, L, A] = WriterT(F0.unit[(L, A)]) + + override def contramap2[A, B, C](fb: WriterT[F, L, B], fc: WriterT[F, L, C])(f: A => (B, C)): WriterT[F, L, A] = + WriterT(F0.contramap2(fb.run, fc.run)((tup: (L, A)) => f(tup._2) match { + case (b, c) => ((tup._1, b), (tup._1, c)) + })) +} + 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..a9927674e5 100644 --- a/core/src/main/scala/cats/instances/eq.scala +++ b/core/src/main/scala/cats/instances/eq.scala @@ -2,11 +2,25 @@ 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 catsDivisibleForEq: Divisible[Eq] = + new Divisible[Eq] { + /** + * Defaults to the trivial equivalence relation + * contracting the type to a point + */ + def unit[A]: Eq[A] = Eq.allEqual - 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) } + /** 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 contramap2[A, B, C](fb: Eq[B], fc: Eq[C])(f: A => (B, C)): Eq[A] = + Eq.instance { (l, r) => + (f(l), f(r)) match { + case (derivedL, derivedR) => + fb.eqv(derivedL._1, derivedR._1) && + fc.eqv(derivedL._2, derivedR._2) + } + } } } diff --git a/core/src/main/scala/cats/instances/equiv.scala b/core/src/main/scala/cats/instances/equiv.scala index 736fdaa12e..a045b56484 100644 --- a/core/src/main/scala/cats/instances/equiv.scala +++ b/core/src/main/scala/cats/instances/equiv.scala @@ -2,17 +2,28 @@ package cats package instances trait EquivInstances { - implicit val catsContravariantSemigroupalEquiv: ContravariantSemigroupal[Equiv] = - new ContravariantSemigroupal[Equiv] { - 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)) - } + implicit val catsDivisibleForEquiv: Divisible[Equiv] = + new Divisible[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 + } - 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) + /** 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 contramap2[A, B, C](fb: Equiv[B], fc: Equiv[C])(f: A => (B, C)): Equiv[A] = + new Equiv[A] { + def equiv(l: A, r: A): Boolean = + (f(l), f(r)) match { + case (derivedL, derivedR) => + fb.equiv(derivedL._1, derivedR._1) && + fc.equiv(derivedL._2, derivedR._2) + } } } } diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index ede97d7857..bfb23160f2 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -43,6 +43,15 @@ private[instances] sealed trait Function1Instances { fa.compose(f) } + implicit def catsStdDivisibleForFunction1[R: Monoid]: Divisible[? => R] = + new Divisible[? => R] { + def unit[A]: A => R = Function.const(Monoid[R].empty) + def contramap2[A, B, C](fb: B => R, fc: C => R)(f: A => (B, C)): A => R = + a => f(a) match { + case (b, c) => Monoid[R].combine(fb(b), fc(c)) + } + } + implicit def catsStdMonadForFunction1[T1]: Monad[T1 => ?] = new Monad[T1 => ?] { def pure[R](r: R): T1 => R = _ => r diff --git a/core/src/main/scala/cats/instances/order.scala b/core/src/main/scala/cats/instances/order.scala index e0dea80414..f226267256 100644 --- a/core/src/main/scala/cats/instances/order.scala +++ b/core/src/main/scala/cats/instances/order.scala @@ -2,21 +2,25 @@ package cats package instances trait OrderInstances extends cats.kernel.OrderToOrderingConversion { - - implicit val catsContravariantSemigroupalForOrder: ContravariantSemigroupal[Order] = - new ContravariantSemigroupal[Order] { + implicit val catsDivisibleForOrder: Divisible[Order] = + new Divisible[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 product[A, B](fa: Order[A], fb: Order[B]): Order[(A, B)] = - new Order[(A, B)] { - def compare(x: (A, B), y: (A, B)): Int = { - val z = fa.compare(x._1, y._1) - if (z == 0) fb.compare(x._2, y._2) else z - } + def contramap2[A, B, C](fb: Order[B], fc: Order[C])(f: A => (B, C)): Order[A] = + new Order[A] { + def compare(x: A, y: A): Int = + (f(x), f(y)) match { + case (derivedL, derivedR) => { + val z = fb.compare(derivedL._1, derivedR._1) + if (z == 0) fc.compare(derivedL._2, derivedR._2) else z + } + } } } } diff --git a/core/src/main/scala/cats/instances/ordering.scala b/core/src/main/scala/cats/instances/ordering.scala index 13240b7cbf..1780548649 100644 --- a/core/src/main/scala/cats/instances/ordering.scala +++ b/core/src/main/scala/cats/instances/ordering.scala @@ -2,20 +2,23 @@ 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 catsDivisibleForOrdering: Divisible[Ordering] = + new Divisible[Ordering] { + /** * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ - 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)] = - new Ordering[(A, B)] { - def compare(x: (A, B), y: (A, B)): Int = { - val z = fa.compare(x._1, y._1) - if (z == 0) fb.compare(x._2, y._2) else z + def unit[A]: Ordering[A] = new Ordering[A] { + def compare(l: A, r: A): Int = 0 + } + + def contramap2[A, B, C](fb: Ordering[B], fc: Ordering[C])(f: A => (B, C)): Ordering[A] = + new Ordering[A] { + def compare(x: A, y: A): Int = (f(x), f(y)) match { + case ((bL, cL), (bR, cR)) => { + val z = fb.compare(bL, bR) + if (z == 0) fc.compare(cL, cR) else z + } } } } diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 1c930bb26f..66f620f69d 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -15,6 +15,7 @@ trait AllSyntax with ComonadSyntax with ComposeSyntax with ContravariantSyntax + with DivisibleSyntax with EitherKSyntax with EitherSyntax with EqSyntax diff --git a/core/src/main/scala/cats/syntax/divisible.scala b/core/src/main/scala/cats/syntax/divisible.scala new file mode 100644 index 0000000000..c1d849ae1f --- /dev/null +++ b/core/src/main/scala/cats/syntax/divisible.scala @@ -0,0 +1,6 @@ +package cats +package syntax + +import cats.Divisible + +trait DivisibleSyntax extends Divisible.ToDivisibleOps diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index cdf41ebc65..00a4093bf3 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -13,6 +13,7 @@ package object syntax { @deprecated("use cats.syntax.semigroupal instead", "1.0.0-RC1") object cartesian extends SemigroupalSyntax object coflatMap extends CoflatMapSyntax + object divisible extends DivisibleSyntax object eitherK extends EitherKSyntax object comonad extends ComonadSyntax object compose extends ComposeSyntax diff --git a/docs/src/main/resources/microsite/data/menu.yml b/docs/src/main/resources/microsite/data/menu.yml index 2ef6321489..804ae3b368 100644 --- a/docs/src/main/resources/microsite/data/menu.yml +++ b/docs/src/main/resources/microsite/data/menu.yml @@ -76,6 +76,10 @@ options: url: typeclasses/invariantmonoidal.html menu_section: variance + - title: Divisible + url: typeclasses/divisible.html + menu_type: typeclasses + - title: Eq url: typeclasses/eq.html menu_type: typeclasses diff --git a/docs/src/main/tut/typeclasses/divisible.md b/docs/src/main/tut/typeclasses/divisible.md new file mode 100644 index 0000000000..cc7b178828 --- /dev/null +++ b/docs/src/main/tut/typeclasses/divisible.md @@ -0,0 +1,83 @@ +--- +layout: docs +title: "Divisible" +section: "typeclasses" +source: "core/src/main/scala/cats/Divisible.scala" +scaladoc: "#cats.Divisible" +--- +# Divisible + +The `Divisible` type class is for contravariant functors that define a +`contramap2` function that looks like: + +```scala +def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] +``` + +This is similar to the `map2` function on the `Apply` typeclass, but + +This is part of what makes people say `Divisible` is a +contravariant version of `Applicative`. + +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 `Divisible` allows you to obtain +a context `F[A]` for the type `A`. + +Examples of `Divisible` instances are [`Eq`](eq.html) and [`Const`](../datatypes/const.html), +but there are also interesting instances for other types. + +## Predicates Have `Divisible` + +An example application would be the case of predicates. Consider the type, + +```tut:book:silent +import cats._ + +import cats.implicits._ + +case class Predicate[A](run: A => Boolean) +``` + +Then, we can exhibit a `Divisible` for `Predicate` by basing it on the +`Monoid` for `Boolean` via `&&` as, + +```tut:book:silent +implicit val divisiblePredicate: Divisible[Predicate] = + new Divisible [Predicate] { + def unit[A]: Predicate[A] = Predicate[A](Function.const(true)) + + def contramap2[A, B, C](fb: Predicate[B], fc: Predicate[C] + )(f: A => (B, C)): Predicate [A] = Predicate( + (a: A) => f(a) match { + case (b, c) => fb.run(b) && fc.run(c) + }) + } +``` + +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)) +``` + +More interestingly, we can combine two predicates using +a `contramap2`. + +```tut:book +case class Transaction(value: Money, payee: String) + +def isEvan: Predicate[String] = Predicate(_ == "Evan") + +def isEvenPaymentToEvan = + isEvenMoney.contramap2(isEvan)( + (trans: Transaction) => (trans.value, trans.payee)) + +isEvenPaymentToEvan.run(Transaction(Money(56), "Evan")) +``` diff --git a/laws/src/main/scala/cats/laws/DivisibleLaws.scala b/laws/src/main/scala/cats/laws/DivisibleLaws.scala new file mode 100644 index 0000000000..7744ffde47 --- /dev/null +++ b/laws/src/main/scala/cats/laws/DivisibleLaws.scala @@ -0,0 +1,38 @@ +package cats +package laws + +import cats.Divisible +import cats.syntax.contravariant._ +import cats.syntax.divisible._ + +/** + * Laws that must hold for any `cats.Divisible`. + */ +trait DivisibleLaws[F[_]] extends ContravariantLaws[F] { + implicit override def F: Divisible[F] + + /** + * The traditional diagonal map + */ + def delta[A](a: A): (A, A) = (a, a) + + def divisibleUnitRight[A](fa: F[A]): IsEq[F[A]] = + fa.contramap2(F.unit)(delta[A]) <-> fa + + def divisibleUnitLeft[A](fa: F[A]): IsEq[F[A]] = + (F.unit).contramap2(fa)(delta[A]) <-> fa + + def divisibleContramap2CompatibleContramapLeft[A, B, C](fa: F[A], f: B => (A, C)): IsEq[F[B]] = + fa.contramap2(F.unit)(f) <-> fa.contramap(f andThen (_._1)) + + def divisibleContramap2CompatibleContramapRight[A, B, C](fa: F[A], f: C => (B, A)): IsEq[F[C]] = + (F.unit).contramap2(fa)(f) <-> fa.contramap(f andThen (_._2)) + + def divisibleContramap2DiagonalAssociates[A](m: F[A], n: F[A], o: F[A]): IsEq[F[A]] = + (m.contramap2(n)(delta[A])).contramap2(o)(delta[A]) <-> m.contramap2(n.contramap2(o)(delta[A]))(delta[A]) +} + +object DivisibleLaws { + def apply[F[_]](implicit ev: Divisible[F]): DivisibleLaws[F] = + new DivisibleLaws[F] { def F: Divisible[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/DivisibleTests.scala b/laws/src/main/scala/cats/laws/discipline/DivisibleTests.scala new file mode 100644 index 0000000000..fe57a8dccf --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/DivisibleTests.scala @@ -0,0 +1,36 @@ +package cats +package laws +package discipline + +import cats.Divisible +import org.scalacheck.{Arbitrary, Cogen} +import org.scalacheck.Prop._ + +trait DivisibleTests[F[_]] extends ContravariantTests[F] { + def laws: DivisibleLaws[F] + + def divisible[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit + arbFA: Arbitrary[F[A]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { + new DefaultRuleSet( + name = "divisible", + parent = Some(contravariant[A, B, C]), + "divisible right unit" -> forAll(laws.divisibleUnitRight[A] _), + "divisible left unit" -> forAll(laws.divisibleUnitLeft[A] _), + "divisible contramap2 compatible contramap left" -> forAll(laws.divisibleContramap2CompatibleContramapLeft[A, B, C] _), + "divisible contramap2 compatible contramap right" -> forAll(laws.divisibleContramap2CompatibleContramapRight[A, B, C] _), + "divisible contramap2 delta associates" -> forAll(laws.divisibleContramap2DiagonalAssociates[A] _) + ) + } +} + +object DivisibleTests { + def apply[F[_]: Divisible]: DivisibleTests[F] = + new DivisibleTests[F] { def laws: DivisibleLaws[F] = DivisibleLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/ConstSuite.scala b/tests/src/test/scala/cats/tests/ConstSuite.scala index 6be8244d75..52a8fcd503 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]", DivisibleTests[Const[String, ?]].divisible[Int, Int, Int]) + checkAll("Divisible[Const[String, ?]]", SerializableTests.serializable(Divisible[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..92bf1c4f57 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] + Divisible[Eq] } { @@ -20,5 +21,6 @@ class EqSuite extends FunSuite { Contravariant[Eq] Semigroupal[Eq] ContravariantSemigroupal[Eq] + Divisible[Eq] } } diff --git a/tests/src/test/scala/cats/tests/EquivSuite.scala b/tests/src/test/scala/cats/tests/EquivSuite.scala index 26fb8271a6..e10df00127 100644 --- a/tests/src/test/scala/cats/tests/EquivSuite.scala +++ b/tests/src/test/scala/cats/tests/EquivSuite.scala @@ -12,8 +12,10 @@ class EquivSuite extends CatsSuite { Contravariant[Equiv] Semigroupal[Equiv] ContravariantSemigroupal[Equiv] + Divisible[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("Divisible[Equiv]", DivisibleTests[Equiv].divisible[Int, Int, Int]) + checkAll("Divisible[Equiv]", SerializableTests.serializable(Divisible[Equiv])) } diff --git a/tests/src/test/scala/cats/tests/FunctionSuite.scala b/tests/src/test/scala/cats/tests/FunctionSuite.scala index 8b26aeb23d..36b2aac5ed 100644 --- a/tests/src/test/scala/cats/tests/FunctionSuite.scala +++ b/tests/src/test/scala/cats/tests/FunctionSuite.scala @@ -101,6 +101,7 @@ 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) + checkAll("Function1[?, Monoid]", DivisibleTests[Function1[?, Long]].divisible[Int, Int, Int]) // serialization tests for the various Function1-related instances checkAll("Semigroup[String => Semi]", SerializableTests.serializable(Semigroup[String => Semi])) @@ -112,4 +113,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("Divisible[Function1[?, Monoid]]", SerializableTests.serializable(Divisible[? => Long])) } diff --git a/tests/src/test/scala/cats/tests/IdTSuite.scala b/tests/src/test/scala/cats/tests/IdTSuite.scala index 7cc192890c..59161dabbd 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,11 @@ class IdTSuite extends CatsSuite { checkAll("Applicative[IdT[ListWrapper, ?]]", SerializableTests.serializable(Applicative[IdT[ListWrapper, ?]])) } + { + checkAll("IdT[Const[String, ?], ?]", DivisibleTests[IdT[Const[String, ?], ?]].divisible[Int, Int, Int]) + checkAll("Divisible[IdT[Const[String, ?], ?]]", SerializableTests.serializable(Divisible[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..3419160892 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 Divisible + val SD = Divisible[StateT[Const[String, ?], String, ?]] + + checkAll("Divisible[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..242b2f3967 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,12 @@ class KleisliSuite extends CatsSuite { checkAll("Alternative[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Alternative[Kleisli[Option, Int, ?]])) } + { + implicit val catsDataDivisibleForKleisli = Kleisli.catsDataDivisibleForKleisli[Const[String, ?], Int] + checkAll("Kleisli[Const[String, ?], Int, Int]", DivisibleTests[Kleisli[Const[String, ?], Int, ?]].divisible[Int, Int, Int]) + checkAll("Divisible[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Divisible[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..dd966b2b34 100644 --- a/tests/src/test/scala/cats/tests/NestedSuite.scala +++ b/tests/src/test/scala/cats/tests/NestedSuite.scala @@ -50,6 +50,12 @@ class NestedSuite extends CatsSuite { checkAll("Contravariant[Nested[Option, Show, ?]]", SerializableTests.serializable(Contravariant[Nested[Option, Show, ?]])) } + { + // Applicative + Divisible functor composition + checkAll("Nested[Option, Const[String, ?], ?]", DivisibleTests[Nested[Option, Const[String, ?], ?]].divisible[Int, Int, Int]) + checkAll("Divisible[Nested[Option, Const[String, ?], ?]", SerializableTests.serializable(Divisible[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..271d2707f2 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,13 @@ class OptionTSuite extends CatsSuite { checkAll("Functor[OptionT[ListWrapper, ?]]", SerializableTests.serializable(Functor[OptionT[ListWrapper, ?]])) } + + { + // F has a Divisible + checkAll("OptionT[Const[String, ?], Int]", DivisibleTests[OptionT[Const[String, ?], ?]].divisible[Int, Int, Int]) + checkAll("Divisible[OptionT[Const[String, ?], Int]]", SerializableTests.serializable(Divisible[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..064f548c9c 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] + Divisible[Order] } checkAll("Int", OrderTests[Int].order) @@ -21,6 +22,7 @@ object OrderSuite { import cats.instances.order._ Invariant[Order] Contravariant[Order] + Divisible[Order] () } diff --git a/tests/src/test/scala/cats/tests/OrderingSuite.scala b/tests/src/test/scala/cats/tests/OrderingSuite.scala index 2af60c5992..ffdc3e88f8 100644 --- a/tests/src/test/scala/cats/tests/OrderingSuite.scala +++ b/tests/src/test/scala/cats/tests/OrderingSuite.scala @@ -12,8 +12,10 @@ class OrderingSuite extends CatsSuite { Contravariant[Ordering] Semigroupal[Ordering] ContravariantSemigroupal[Ordering] + Divisible[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("Divisible[Ordering]", DivisibleTests[Ordering].divisible[Int, Int, Int]) + checkAll("Divisible[Ordering]", SerializableTests.serializable(Divisible[Ordering])) } diff --git a/tests/src/test/scala/cats/tests/Tuple2KSuite.scala b/tests/src/test/scala/cats/tests/Tuple2KSuite.scala index 1795218548..3ab0d4b72e 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,9 @@ 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]", DivisibleTests[λ[α => Tuple2K[Const[String, ?], Const[Int, ?], α]]].divisible[Int, Int, Int]) + checkAll("Divisible[Tuple2K[Const[String, ?], Const[Int, ?], Int]]", SerializableTests.serializable(Divisible[λ[α => 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..80cbc163fd 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,11 @@ class WriterTSuite extends CatsSuite { checkAll("MonadError[WriterT[Option, ListWrapper[Int], ?], Unit]", SerializableTests.serializable(MonadError[WriterT[Option, ListWrapper[Int], ?], Unit])) } + { + // F has a Divisible + Divisible[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, ?]])) } From 3db8a829522e5460870d3b287be33d5972c54814 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Wed, 22 Nov 2017 20:22:13 -0800 Subject: [PATCH 02/14] Rename to ContravariantMonoidal, Base on product Renames to ContravariantMonoidal Derives contramap2 from product, contramap Moves contramap2 to ContravariantSemigroupal --- core/src/main/scala/cats/Composed.scala | 11 ++-- .../scala/cats/ContravariantMonoidal.scala | 32 ++++++++++++ .../scala/cats/ContravariantSemigroupal.scala | 3 ++ core/src/main/scala/cats/Divisible.scala | 52 ------------------- core/src/main/scala/cats/data/Const.scala | 8 +-- core/src/main/scala/cats/data/IdT.scala | 15 +++--- .../main/scala/cats/data/IndexedStateT.scala | 16 ++++-- core/src/main/scala/cats/data/Kleisli.scala | 15 +++--- core/src/main/scala/cats/data/Nested.scala | 17 +++--- core/src/main/scala/cats/data/OptionT.scala | 21 +++++--- core/src/main/scala/cats/data/Tuple2K.scala | 20 +++---- core/src/main/scala/cats/data/WriterT.scala | 24 +++++---- core/src/main/scala/cats/instances/eq.scala | 19 ++++--- .../src/main/scala/cats/instances/equiv.scala | 24 +++++---- .../main/scala/cats/instances/function.scala | 12 +++-- .../src/main/scala/cats/instances/order.scala | 24 +++++---- .../main/scala/cats/instances/ordering.scala | 21 +++++--- core/src/main/scala/cats/syntax/all.scala | 3 +- .../cats/syntax/contravariantMonoidal.scala | 6 +++ .../syntax/contravariantSemigroupal.scala | 6 +++ .../main/scala/cats/syntax/divisible.scala | 6 --- core/src/main/scala/cats/syntax/package.scala | 3 +- .../main/resources/microsite/data/menu.yml | 6 +-- ...visible.md => contravariantsemigroupal.md} | 27 +++++----- .../cats/laws/ContravariantMonoidalLaws.scala | 38 ++++++++++++++ .../main/scala/cats/laws/DivisibleLaws.scala | 38 -------------- .../ContravariantMonoidalTests.scala | 41 +++++++++++++++ .../cats/laws/discipline/DivisibleTests.scala | 36 ------------- .../test/scala/cats/tests/ConstSuite.scala | 4 +- tests/src/test/scala/cats/tests/EqSuite.scala | 4 +- .../test/scala/cats/tests/EquivSuite.scala | 8 +-- .../test/scala/cats/tests/FunctionSuite.scala | 4 +- .../src/test/scala/cats/tests/IdTSuite.scala | 6 ++- .../scala/cats/tests/IndexedStateTSuite.scala | 6 +-- .../test/scala/cats/tests/KleisliSuite.scala | 8 +-- .../test/scala/cats/tests/NestedSuite.scala | 8 +-- .../test/scala/cats/tests/OptionTSuite.scala | 7 +-- .../test/scala/cats/tests/OrderSuite.scala | 4 +- .../test/scala/cats/tests/OrderingSuite.scala | 8 +-- .../test/scala/cats/tests/Tuple2KSuite.scala | 6 ++- .../test/scala/cats/tests/WriterTSuite.scala | 4 +- 41 files changed, 343 insertions(+), 278 deletions(-) create mode 100644 core/src/main/scala/cats/ContravariantMonoidal.scala delete mode 100644 core/src/main/scala/cats/Divisible.scala create mode 100644 core/src/main/scala/cats/syntax/contravariantMonoidal.scala create mode 100644 core/src/main/scala/cats/syntax/contravariantSemigroupal.scala delete mode 100644 core/src/main/scala/cats/syntax/divisible.scala rename docs/src/main/tut/typeclasses/{divisible.md => contravariantsemigroupal.md} (69%) create mode 100644 laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala delete mode 100644 laws/src/main/scala/cats/laws/DivisibleLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ContravariantMonoidalTests.scala delete mode 100644 laws/src/main/scala/cats/laws/discipline/DivisibleTests.scala diff --git a/core/src/main/scala/cats/Composed.scala b/core/src/main/scala/cats/Composed.scala index 1cb67a4a57..918e6969e7 100644 --- a/core/src/main/scala/cats/Composed.scala +++ b/core/src/main/scala/cats/Composed.scala @@ -113,14 +113,17 @@ private[cats] trait ComposedContravariantCovariant[F[_], G[_]] extends Contravar F.contramap(fga)(gb => G.map(gb)(f)) } -private[cats] trait ComposedApplicativeDivisible[F[_], G[_]] extends Divisible[λ[α => F[G[α]]]] { outer => +private[cats] trait ComposedApplicativeContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[λ[α => F[G[α]]]] { outer => def F: Applicative[F] - def G: Divisible[G] + def G: ContravariantMonoidal[G] override def unit[A]: F[G[A]] = F.pure(G.unit) - override def contramap2[A, B, C](fb: F[G[B]], fc: F[G[C]])(f: A => (B, C)): F[G[A]] = - F.ap2(F.pure((gb: G[B], gc: G[C]) => G.contramap2(gb, gc)(f)))(fb, fc) + override def contramap[A, B](fa: F[G[A]])(f: B => A) = + F.map(fa)(G.contramap(_)(f)) + + override def product[A, B](fa: F[G[A]], fb: F[G[B]]) = + F.map2(fa, fb)(G.product(_,_)) } private[cats] trait ComposedSemigroupal[F[_], G[_]] extends ContravariantSemigroupal[λ[α => F[G[α]]]] with ComposedContravariantCovariant[F, G] { outer => diff --git a/core/src/main/scala/cats/ContravariantMonoidal.scala b/core/src/main/scala/cats/ContravariantMonoidal.scala new file mode 100644 index 0000000000..b20e8d139b --- /dev/null +++ b/core/src/main/scala/cats/ContravariantMonoidal.scala @@ -0,0 +1,32 @@ +package cats + +import simulacrum.typeclass + +/** + * ContravariantMonoidal functors + * + * 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] + + def liftContravariant[A, B](f: A => B): F[B] => F[A] = + contramap2(unit, _: F[B])(((b: B) => (b, b)) compose f) + + // Technically, this is not correct, as the Applicative is composed with the ContravariantMonoidal, not the other way around + def composeApplicative[G[_]: Applicative]: ContravariantMonoidal[λ[α => G[F[α]]]] = + new ComposedApplicativeContravariantMonoidal[G, F] { + val F = Applicative[G] + val G = self + } +} + diff --git a/core/src/main/scala/cats/ContravariantSemigroupal.scala b/core/src/main/scala/cats/ContravariantSemigroupal.scala index e90d2d14ac..0655df4e09 100644 --- a/core/src/main/scala/cats/ContravariantSemigroupal.scala +++ b/core/src/main/scala/cats/ContravariantSemigroupal.scala @@ -12,4 +12,7 @@ import simulacrum.typeclass def F = self def G = Functor[G] } + + def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] = + contramap(product(fb, fc))(f) } diff --git a/core/src/main/scala/cats/Divisible.scala b/core/src/main/scala/cats/Divisible.scala deleted file mode 100644 index 72080cd4b0..0000000000 --- a/core/src/main/scala/cats/Divisible.scala +++ /dev/null @@ -1,52 +0,0 @@ -package cats - -import simulacrum.typeclass - -/** - * Divisible functors - * - * Must obey the laws defined in cats.laws.DivisibleLaws. - * - * Based on ekmett's contravariant library: - * https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html - */ -@typeclass trait Divisible[F[_]] extends ContravariantSemigroupal[F] { self => - - /** - * `unit` produces an instance of `F` for any type `A` - */ - def unit[A]: F[A] - - /** - * `contramap2` - * - * Given two values in the Divisible context, and a function producing a value of both types, - * yields an element of the domain of the function lifted into the context. - */ - def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] - - - // Technically, this is not correct, as the Applicative is composed with the Divisible, not the other way around - def composeApplicative[G[_]: Applicative]: Divisible[λ[α => G[F[α]]]] = - new ComposedApplicativeDivisible[G, F] { - val F = Applicative[G] - val G = self - } - - /** - * Allows two instances to packaged into an instance over the product - */ - override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = - contramap2(fa, fb)(identity) - - /** - * Lifts a function into the Divisible contravariantly - */ - def liftD[A, B](f: A => B): F[B] => F[A] = - contramap2(unit, _: F[B])(((b: B) => (b, b)) compose f) - - - override def contramap[A, B](fa: F[A])(f: (B) => A): F[B] = - liftD(f)(fa) -} - diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 29f311e5c4..a4695594ca 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -70,10 +70,12 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { fa.retag[B] } - implicit def catsDataDivisibleForConst[D: Monoid]: Divisible[Const[D, ?]] = new Divisible[Const[D, ?]] { + implicit def catsDataContravariantMonoidalForConst[D: Monoid]: ContravariantMonoidal[Const[D, ?]] = new ContravariantMonoidal[Const[D, ?]] { override def unit[A] = Const.empty[D, A] - override def contramap2[A, B, C](fb: Const[D, B], fc: Const[D, C])(f: A => (B, C)): Const[D, A] = - fb.retag[A] combine fc.retag[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, ?]] { diff --git a/core/src/main/scala/cats/data/IdT.scala b/core/src/main/scala/cats/data/IdT.scala index 1407da7da2..7a79b014dc 100644 --- a/core/src/main/scala/cats/data/IdT.scala +++ b/core/src/main/scala/cats/data/IdT.scala @@ -73,13 +73,16 @@ 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 IdTDivisible[F[_]] extends Divisible[IdT[F, ?]] { - implicit val F0: Divisible[F] +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 contramap2[A, B, C](fb: IdT[F, B], fc: IdT[F, C])(f: A => (B, C)): IdT[F, A] = - IdT(F0.contramap2(fb.value, fc.value)(f)) + 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] { @@ -133,8 +136,8 @@ private[data] sealed trait IdTNonEmptyTraverse[F[_]] extends IdTTraverse[F] with } private[data] sealed abstract class IdTInstances6 { - implicit def catsDataDivisibleForIdT[F[_]](implicit F: Divisible[F]): Divisible[IdT[F, ?]] = - new IdTDivisible[F] { implicit val F0: Divisible[F] = F } + 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 { diff --git a/core/src/main/scala/cats/data/IndexedStateT.scala b/core/src/main/scala/cats/data/IndexedStateT.scala index c985ec431a..b57001f993 100644 --- a/core/src/main/scala/cats/data/IndexedStateT.scala +++ b/core/src/main/scala/cats/data/IndexedStateT.scala @@ -231,9 +231,9 @@ private[data] sealed abstract class IndexedStateTInstances extends IndexedStateT 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 catsDataDivisibleForIndexedStateT[F[_], S](implicit FD: Divisible[F], - FA: Applicative[F]): Divisible[IndexedStateT[F, S, S, ?]] = - new IndexedStateTDivisible[F, S] { implicit def F = FD; 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 { @@ -366,13 +366,19 @@ 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 IndexedStateTDivisible[F[_], S] extends Divisible[IndexedStateT[F, S, S, ?]]{ - implicit def F: Divisible[F] +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) + override 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) => diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index f8ff68fafb..f9fbc5bfb3 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -166,8 +166,8 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 implicit def catsDataAlternativeForKleisli[F[_], A](implicit F0: Alternative[F]): Alternative[Kleisli[F, A, ?]] = new KleisliAlternative[F, A] { def F: Alternative[F] = F0 } - implicit def catsDataDivisibleForKleisli[F[_], A](implicit F0: Divisible[F]): Divisible[Kleisli[F, A, ?]] = - new KleisliDivisible[F, A] { def F: Divisible[F] = F0 } + 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 KleisliInstances3 extends KleisliInstances4 { @@ -297,13 +297,16 @@ private[data] trait KleisliAlternative[F[_], A] extends Alternative[Kleisli[F, A implicit def F: Alternative[F] } -private[data] sealed trait KleisliDivisible[F[_], D] extends Divisible[Kleisli[F, D, ?]] { - implicit def F: Divisible[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 contramap2[A, B, C](fb: Kleisli[F, D, B], fc: Kleisli[F, D, C])(f: A => (B, C)): Kleisli[F, D, A] = - Kleisli(d => F.contramap2(fb.run(d), fc.run(d))(f)) + 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] { diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index c7c8c74201..21b906d346 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -63,9 +63,9 @@ private[data] sealed abstract class NestedInstances1 extends NestedInstances2 { val FG: Functor[λ[α => F[G[α]]]] = Contravariant[F].compose[G] } - implicit def catsDataDivisibleForApplicativeForNested[F[_]: Applicative, G[_]: Divisible]: Divisible[Nested[F, G, ?]] = - new NestedDivisible[F, G] { - val FG: Divisible[λ[α => F[G[α]]]] = Divisible[G].composeApplicative[F] + implicit def catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal]: ContravariantMonoidal[Nested[F, G, ?]] = + new NestedContravariantMonoidal[F, G] { + val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = ContravariantMonoidal[G].composeApplicative[F] } } @@ -269,11 +269,14 @@ private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested Nested(FG.contramap(fga.value)(f)) } -private[data] trait NestedDivisible[F[_], G[_]] extends Divisible[Nested[F, G, ?]] { - def FG: Divisible[λ[α => F[G[α]]]] +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 contramap2[A, B, C](fb: Nested[F, G, B], fc: Nested[F, G, C])(f: A => (B, C)): Nested[F, G, A] = - Nested(FG.contramap2(fb.value, fc.value)(f)) + 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 c7a4e12f99..0acd5fc2d0 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -221,8 +221,8 @@ 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 catsDataDivisibleForOptionT[F[_]](implicit F0: Divisible[F]): Divisible[OptionT[F, ?]] = - new OptionTDivisible[F] { 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 } @@ -284,14 +284,21 @@ private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] wi OptionT(F.handleErrorWith(fa.value)(f(_).value)) } -private trait OptionTDivisible[F[_]] extends Divisible[OptionT[F, ?]] { - def F: Divisible[F] +private trait OptionTContravariantMonoidal[F[_]] extends ContravariantMonoidal[OptionT[F, ?]] { + def F: ContravariantMonoidal[F] override def unit[A]: OptionT[F, A] = OptionT (F.unit) - // Really this wants to be written in terms of arrow :/ - override def contramap2[A, B, C](fb: OptionT[F, B], fc: OptionT[F, C])(f: A => (B, C)): OptionT[F, A] = - OptionT(F.contramap2(fb.value, fc.value)(x => (x.map(f andThen (_._1)), x.map(f andThen (_._2))))) + 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, ?]] { diff --git a/core/src/main/scala/cats/data/Tuple2K.scala b/core/src/main/scala/cats/data/Tuple2K.scala index e966c52a2c..ad0c9a5907 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -33,10 +33,10 @@ private[data] sealed abstract class Tuple2KInstances extends Tuple2KInstances0 { def F: Contravariant[F] = FC def G: Contravariant[G] = GC } - implicit def catsDataDivisibleForTuple2k[F[_], G[_]](implicit FD: Divisible[F], GD: Divisible[G]): Divisible[λ[α => Tuple2K[F, G, α]]] = - new Tuple2KDivisible[F, G] { - def F: Divisible[F] = FD - def G: Divisible[G] = GD + 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 } } @@ -128,12 +128,14 @@ 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 Tuple2KDivisible[F[_], G[_]] extends Divisible[λ[α => Tuple2K[F, G, α]]] { - def F: Divisible[F] - def G: Divisible[G] +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 contramap2[A, B, C](fb: Tuple2K[F, G, B], fc: Tuple2K[F, G, C])(f: A => (B, C)): Tuple2K[F, G, A] = - Tuple2K(F.contramap2(fb.first, fc.first)(f), G.contramap2(fb.second, fc.second)(f)) + 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] { diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 4da02344ef..2bfa785108 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -179,9 +179,9 @@ private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 implicit val L0: Monoid[L] = L } - implicit def catsDataDivisibleForWriterT[F[_], L](implicit F: Divisible[F]): Divisible[WriterT[F, L, ?]] = - new WriterTDivisible[F, L] { - implicit val F0: Divisible[F] = F + implicit def catsDataContravariantMonoidalForWriterT[F[_], L](implicit F: ContravariantMonoidal[F]): ContravariantMonoidal[WriterT[F, L, ?]] = + new WriterTContravariantMonoidal[F, L] { + implicit val F0: ContravariantMonoidal[F] = F } } @@ -358,15 +358,21 @@ private[data] sealed trait WriterTAlternative[F[_], L] extends Alternative[Write override implicit def F0: Alternative[F] } -private[data] sealed trait WriterTDivisible[F[_], L] extends Divisible[WriterT[F, L, ?]] { - implicit def F0: Divisible[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 contramap2[A, B, C](fb: WriterT[F, L, B], fc: WriterT[F, L, C])(f: A => (B, C)): WriterT[F, L, A] = - WriterT(F0.contramap2(fb.run, fc.run)((tup: (L, A)) => f(tup._2) match { - case (b, c) => ((tup._1, b), (tup._1, c)) - })) + 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]] { diff --git a/core/src/main/scala/cats/instances/eq.scala b/core/src/main/scala/cats/instances/eq.scala index a9927674e5..80770ad05f 100644 --- a/core/src/main/scala/cats/instances/eq.scala +++ b/core/src/main/scala/cats/instances/eq.scala @@ -2,8 +2,8 @@ package cats package instances trait EqInstances { - implicit val catsDivisibleForEq: Divisible[Eq] = - new Divisible[Eq] { + implicit val catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] = + new ContravariantMonoidal[Eq] { /** * Defaults to the trivial equivalence relation * contracting the type to a point @@ -14,12 +14,17 @@ trait EqInstances { * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ - def contramap2[A, B, C](fb: Eq[B], fc: Eq[C])(f: A => (B, C)): Eq[A] = + def contramap[A, B](fa: Eq[A])(f: B => A): Eq[B] = + Eq.instance { (l: B, r: B) => + fa.eqv(f(l), f(r)) + } + + def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] = Eq.instance { (l, r) => - (f(l), f(r)) match { - case (derivedL, derivedR) => - fb.eqv(derivedL._1, derivedR._1) && - fc.eqv(derivedL._2, derivedR._2) + (l, r) match { + case ((aL, bL), (aR, bR)) => + fa.eqv(aL, aR) && + fb.eqv(bL, bR) } } } diff --git a/core/src/main/scala/cats/instances/equiv.scala b/core/src/main/scala/cats/instances/equiv.scala index a045b56484..75880fed0f 100644 --- a/core/src/main/scala/cats/instances/equiv.scala +++ b/core/src/main/scala/cats/instances/equiv.scala @@ -2,8 +2,8 @@ package cats package instances trait EquivInstances { - implicit val catsDivisibleForEquiv: Divisible[Equiv] = - new Divisible[Equiv] { + implicit val catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] = + new ContravariantMonoidal[Equiv] { /** * Defaults to trivially contracting the type * to a point @@ -16,13 +16,19 @@ trait EquivInstances { * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ - def contramap2[A, B, C](fb: Equiv[B], fc: Equiv[C])(f: A => (B, C)): Equiv[A] = - new Equiv[A] { - def equiv(l: A, r: A): Boolean = - (f(l), f(r)) match { - case (derivedL, derivedR) => - fb.equiv(derivedL._1, derivedR._1) && - fc.equiv(derivedL._2, derivedR._2) + def contramap[A, B](fa: Equiv[A])(f: B => A): Equiv[B] = + new Equiv[B] { + 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(l: (A, B), r: (A, B)): Boolean = + (l, r) match { + case ((aL, bL), (aR, bR)) => + fa.equiv(aL, aR) && + fb.equiv(bR, bL) } } } diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index bfb23160f2..4dfc3a0ba4 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -43,12 +43,14 @@ private[instances] sealed trait Function1Instances { fa.compose(f) } - implicit def catsStdDivisibleForFunction1[R: Monoid]: Divisible[? => R] = - new Divisible[? => R] { + implicit def catsStdContravariantMonoidalForFunction1[R: Monoid]: ContravariantMonoidal[? => R] = + new ContravariantMonoidal[? => R] { def unit[A]: A => R = Function.const(Monoid[R].empty) - def contramap2[A, B, C](fb: B => R, fc: C => R)(f: A => (B, C)): A => R = - a => f(a) match { - case (b, c) => Monoid[R].combine(fb(b), fc(c)) + 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)) } } diff --git a/core/src/main/scala/cats/instances/order.scala b/core/src/main/scala/cats/instances/order.scala index f226267256..79d322fc3a 100644 --- a/core/src/main/scala/cats/instances/order.scala +++ b/core/src/main/scala/cats/instances/order.scala @@ -2,8 +2,8 @@ package cats package instances trait OrderInstances extends cats.kernel.OrderToOrderingConversion { - implicit val catsDivisibleForOrder: Divisible[Order] = - new Divisible[Order] { + implicit val catsContravariantMonoidalForOrder: ContravariantMonoidal[Order] = + new ContravariantMonoidal[Order] { /** * Provides trivial order */ @@ -12,13 +12,19 @@ trait OrderInstances extends cats.kernel.OrderToOrderingConversion { * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ - def contramap2[A, B, C](fb: Order[B], fc: Order[C])(f: A => (B, C)): Order[A] = - new Order[A] { - def compare(x: A, y: A): Int = - (f(x), f(y)) match { - case (derivedL, derivedR) => { - val z = fb.compare(derivedL._1, derivedR._1) - if (z == 0) fc.compare(derivedL._2, derivedR._2) else z + def contramap[A, B](fa: Order[A])(f: B => A): Order[B] = + new Order[B] { + def compare(x: B, y: B): Int = + fa.compare(f(x), f(y)) + } + + def product[A, B](fa: Order[A], fb: Order[B]): Order[(A, B)] = + new Order[(A, B)] { + def compare(x: (A, B), y: (A, B)): Int = + (x, y) match { + case ((aL, bL), (aR, bR)) => { + val z = fa.compare(aL, aR) + if (z == 0) fb.compare(bL, bR) else z } } } diff --git a/core/src/main/scala/cats/instances/ordering.scala b/core/src/main/scala/cats/instances/ordering.scala index 1780548649..28850e3a9d 100644 --- a/core/src/main/scala/cats/instances/ordering.scala +++ b/core/src/main/scala/cats/instances/ordering.scala @@ -2,8 +2,8 @@ package cats package instances trait OrderingInstances { - implicit val catsDivisibleForOrdering: Divisible[Ordering] = - new Divisible[Ordering] { + 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) */ @@ -12,12 +12,17 @@ trait OrderingInstances { def compare(l: A, r: A): Int = 0 } - def contramap2[A, B, C](fb: Ordering[B], fc: Ordering[C])(f: A => (B, C)): Ordering[A] = - new Ordering[A] { - def compare(x: A, y: A): Int = (f(x), f(y)) match { - case ((bL, cL), (bR, cR)) => { - val z = fb.compare(bL, bR) - if (z == 0) fc.compare(cL, cR) else z + def contramap[A, B](fa: Ordering[A])(f: B => A): Ordering[B] = + new Ordering[B] { + def compare(x: B, y: B) = fa.compare(f(x), f(y)) + } + + def product[A, B](fa: Ordering[A], fb: Ordering[B]): Ordering[(A, B)] = + new Ordering[(A, B)] { + def compare(x: (A, B), y: (A, B)): Int = (x, y) match { + case ((aL, bL), (aR, bR)) => { + val z = fa.compare(aL, aR) + if (z == 0) fb.compare(bL, bR) else z } } } diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 66f620f69d..e2ae40e6c5 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -15,7 +15,8 @@ trait AllSyntax with ComonadSyntax with ComposeSyntax with ContravariantSyntax - with DivisibleSyntax + 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..db1d73c646 --- /dev/null +++ b/core/src/main/scala/cats/syntax/contravariantMonoidal.scala @@ -0,0 +1,6 @@ +package cats +package syntax + +import cats.ContravariantMonoidal + +trait ContravariantMonoidalSyntax extends ContravariantMonoidal.ToContravariantMonoidalOps 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..5dc6f12e0a --- /dev/null +++ b/core/src/main/scala/cats/syntax/contravariantSemigroupal.scala @@ -0,0 +1,6 @@ +package cats +package syntax + +import cats.ContravariantSemigroupal + +trait ContravariantSemigroupalSyntax extends ContravariantSemigroupal.ToContravariantSemigroupalOps diff --git a/core/src/main/scala/cats/syntax/divisible.scala b/core/src/main/scala/cats/syntax/divisible.scala deleted file mode 100644 index c1d849ae1f..0000000000 --- a/core/src/main/scala/cats/syntax/divisible.scala +++ /dev/null @@ -1,6 +0,0 @@ -package cats -package syntax - -import cats.Divisible - -trait DivisibleSyntax extends Divisible.ToDivisibleOps diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 00a4093bf3..dd1cceb060 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -13,11 +13,12 @@ package object syntax { @deprecated("use cats.syntax.semigroupal instead", "1.0.0-RC1") object cartesian extends SemigroupalSyntax object coflatMap extends CoflatMapSyntax - object divisible extends DivisibleSyntax object eitherK extends EitherKSyntax 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 804ae3b368..5bb8704986 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: ContravariantSemigroupal + url: typeclasses/contravariantsemigroupal.html + menu_type: typeclasses - title: Invariant url: typeclasses/invariant.html menu_section: variance @@ -76,9 +79,6 @@ options: url: typeclasses/invariantmonoidal.html menu_section: variance - - title: Divisible - url: typeclasses/divisible.html - menu_type: typeclasses - title: Eq url: typeclasses/eq.html diff --git a/docs/src/main/tut/typeclasses/divisible.md b/docs/src/main/tut/typeclasses/contravariantsemigroupal.md similarity index 69% rename from docs/src/main/tut/typeclasses/divisible.md rename to docs/src/main/tut/typeclasses/contravariantsemigroupal.md index cc7b178828..f860ae4b8e 100644 --- a/docs/src/main/tut/typeclasses/divisible.md +++ b/docs/src/main/tut/typeclasses/contravariantsemigroupal.md @@ -1,33 +1,30 @@ --- layout: docs -title: "Divisible" +title: "Contravariant Semigroupal" section: "typeclasses" -source: "core/src/main/scala/cats/Divisible.scala" -scaladoc: "#cats.Divisible" +source: "core/src/main/scala/cats/ContravariantSemigroupal.scala" +scaladoc: "#cats.ContravariantSemigroupal" --- -# Divisible +# Contravariant Semigroupal -The `Divisible` type class is for contravariant functors that define a +The `ContravariantSemigroupal` type class is for contravariant functors that can define a `contramap2` function that looks like: ```scala def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] ``` -This is similar to the `map2` function on the `Apply` typeclass, but - -This is part of what makes people say `Divisible` is a -contravariant version of `Applicative`. +This is similar to the `map2` function on the `Apply` 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 `Divisible` allows you to obtain +from a type `A`, then `ContravariantMonoidal` allows you to obtain a context `F[A]` for the type `A`. -Examples of `Divisible` instances are [`Eq`](eq.html) and [`Const`](../datatypes/const.html), +Examples of `ContravariantMonoidal` instances are [`Eq`](eq.html) and [`Const`](../datatypes/const.html), but there are also interesting instances for other types. -## Predicates Have `Divisible` +## Predicates Have `ContravariantSemigroupal` An example application would be the case of predicates. Consider the type, @@ -39,12 +36,12 @@ import cats.implicits._ case class Predicate[A](run: A => Boolean) ``` -Then, we can exhibit a `Divisible` for `Predicate` by basing it on the +Then, we can exhibit a `ContravariantSemigroupal` for `Predicate` by basing it on the `Monoid` for `Boolean` via `&&` as, ```tut:book:silent -implicit val divisiblePredicate: Divisible[Predicate] = - new Divisible [Predicate] { +implicit val contravariantSemigroupalPredicate: ContravariantSemigroupal[Predicate] = + new ContravariantMonoidal [Predicate] { def unit[A]: Predicate[A] = Predicate[A](Function.const(true)) def contramap2[A, B, C](fb: Predicate[B], fc: Predicate[C] 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..29a8f8f80c --- /dev/null +++ b/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala @@ -0,0 +1,38 @@ +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 ContravariantLaws[F] { + implicit override def F: ContravariantMonoidal[F] + + /** + * The traditional diagonal map + */ + def delta[A](a: A): (A, A) = (a, a) + + def contravariantMonoidalUnitRight[A](fa: F[A]): IsEq[F[A]] = + fa.contramap2(F.unit)(delta[A]) <-> fa + + def contravariantMonoidalUnitLeft[A](fa: F[A]): IsEq[F[A]] = + (F.unit).contramap2(fa)(delta[A]) <-> fa + + def contravariantMonoidalContramap2CompatibleContramapLeft[A, B, C](fa: F[A], f: B => (A, C)): IsEq[F[B]] = + fa.contramap2(F.unit)(f) <-> fa.contramap(f andThen (_._1)) + + def contravariantMonoidalContramap2CompatibleContramapRight[A, B, C](fa: F[A], f: C => (B, A)): IsEq[F[C]] = + (F.unit).contramap2(fa)(f) <-> fa.contramap(f andThen (_._2)) + + def contravariantMonoidalContramap2DiagonalAssociates[A](m: F[A], n: F[A], o: F[A]): IsEq[F[A]] = + (m.contramap2(n)(delta[A])).contramap2(o)(delta[A]) <-> m.contramap2(n.contramap2(o)(delta[A]))(delta[A]) +} + +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/DivisibleLaws.scala b/laws/src/main/scala/cats/laws/DivisibleLaws.scala deleted file mode 100644 index 7744ffde47..0000000000 --- a/laws/src/main/scala/cats/laws/DivisibleLaws.scala +++ /dev/null @@ -1,38 +0,0 @@ -package cats -package laws - -import cats.Divisible -import cats.syntax.contravariant._ -import cats.syntax.divisible._ - -/** - * Laws that must hold for any `cats.Divisible`. - */ -trait DivisibleLaws[F[_]] extends ContravariantLaws[F] { - implicit override def F: Divisible[F] - - /** - * The traditional diagonal map - */ - def delta[A](a: A): (A, A) = (a, a) - - def divisibleUnitRight[A](fa: F[A]): IsEq[F[A]] = - fa.contramap2(F.unit)(delta[A]) <-> fa - - def divisibleUnitLeft[A](fa: F[A]): IsEq[F[A]] = - (F.unit).contramap2(fa)(delta[A]) <-> fa - - def divisibleContramap2CompatibleContramapLeft[A, B, C](fa: F[A], f: B => (A, C)): IsEq[F[B]] = - fa.contramap2(F.unit)(f) <-> fa.contramap(f andThen (_._1)) - - def divisibleContramap2CompatibleContramapRight[A, B, C](fa: F[A], f: C => (B, A)): IsEq[F[C]] = - (F.unit).contramap2(fa)(f) <-> fa.contramap(f andThen (_._2)) - - def divisibleContramap2DiagonalAssociates[A](m: F[A], n: F[A], o: F[A]): IsEq[F[A]] = - (m.contramap2(n)(delta[A])).contramap2(o)(delta[A]) <-> m.contramap2(n.contramap2(o)(delta[A]))(delta[A]) -} - -object DivisibleLaws { - def apply[F[_]](implicit ev: Divisible[F]): DivisibleLaws[F] = - new DivisibleLaws[F] { def F: Divisible[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..bf4e5bbe7f --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ContravariantMonoidalTests.scala @@ -0,0 +1,41 @@ +package cats +package laws +package discipline + +import cats.ContravariantMonoidal +import org.scalacheck.{Arbitrary, Cogen} +import org.scalacheck.Prop._ + +trait ContravariantMonoidalTests[F[_]] extends ContravariantTests[F] { + def laws: ContravariantMonoidalLaws[F] + + def contravariantMonoidal[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit + arbFA: Arbitrary[F[A]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { + new DefaultRuleSet( + name = "contravariantMonoidal", + parent = Some(contravariant[A, B, C]), + "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] _), + "contravariantMonoidal contramap2 delta associates" -> + forAll(laws.contravariantMonoidalContramap2DiagonalAssociates[A] _) + ) + } +} + +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/DivisibleTests.scala b/laws/src/main/scala/cats/laws/discipline/DivisibleTests.scala deleted file mode 100644 index fe57a8dccf..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/DivisibleTests.scala +++ /dev/null @@ -1,36 +0,0 @@ -package cats -package laws -package discipline - -import cats.Divisible -import org.scalacheck.{Arbitrary, Cogen} -import org.scalacheck.Prop._ - -trait DivisibleTests[F[_]] extends ContravariantTests[F] { - def laws: DivisibleLaws[F] - - def divisible[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - arbFA: Arbitrary[F[A]], - CogenA: Cogen[A], - CogenB: Cogen[B], - CogenC: Cogen[C], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]] - ): RuleSet = { - new DefaultRuleSet( - name = "divisible", - parent = Some(contravariant[A, B, C]), - "divisible right unit" -> forAll(laws.divisibleUnitRight[A] _), - "divisible left unit" -> forAll(laws.divisibleUnitLeft[A] _), - "divisible contramap2 compatible contramap left" -> forAll(laws.divisibleContramap2CompatibleContramapLeft[A, B, C] _), - "divisible contramap2 compatible contramap right" -> forAll(laws.divisibleContramap2CompatibleContramapRight[A, B, C] _), - "divisible contramap2 delta associates" -> forAll(laws.divisibleContramap2DiagonalAssociates[A] _) - ) - } -} - -object DivisibleTests { - def apply[F[_]: Divisible]: DivisibleTests[F] = - new DivisibleTests[F] { def laws: DivisibleLaws[F] = DivisibleLaws[F] } -} diff --git a/tests/src/test/scala/cats/tests/ConstSuite.scala b/tests/src/test/scala/cats/tests/ConstSuite.scala index 52a8fcd503..116e4f8e59 100644 --- a/tests/src/test/scala/cats/tests/ConstSuite.scala +++ b/tests/src/test/scala/cats/tests/ConstSuite.scala @@ -45,8 +45,8 @@ 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]", DivisibleTests[Const[String, ?]].divisible[Int, Int, Int]) - checkAll("Divisible[Const[String, ?]]", SerializableTests.serializable(Divisible[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 92bf1c4f57..56fea4358c 100644 --- a/tests/src/test/scala/cats/tests/EqSuite.scala +++ b/tests/src/test/scala/cats/tests/EqSuite.scala @@ -12,7 +12,7 @@ class EqSuite extends FunSuite { Contravariant[Eq] Semigroupal[Eq] ContravariantSemigroupal[Eq] - Divisible[Eq] + ContravariantMonoidal[Eq] } { @@ -21,6 +21,6 @@ class EqSuite extends FunSuite { Contravariant[Eq] Semigroupal[Eq] ContravariantSemigroupal[Eq] - Divisible[Eq] + ContravariantMonoidal[Eq] } } diff --git a/tests/src/test/scala/cats/tests/EquivSuite.scala b/tests/src/test/scala/cats/tests/EquivSuite.scala index e10df00127..86c8e0ec31 100644 --- a/tests/src/test/scala/cats/tests/EquivSuite.scala +++ b/tests/src/test/scala/cats/tests/EquivSuite.scala @@ -12,10 +12,12 @@ class EquivSuite extends CatsSuite { Contravariant[Equiv] Semigroupal[Equiv] ContravariantSemigroupal[Equiv] - Divisible[Equiv] + ContravariantMonoidal[Equiv] checkAll("Contravariant[Equiv]", ContravariantTests[Equiv].contravariant[Int, Int, Int]) checkAll("Semigroupal[Equiv]", SemigroupalTests[Equiv].semigroupal[Int, Int, Int]) - checkAll("Divisible[Equiv]", DivisibleTests[Equiv].divisible[Int, Int, Int]) - checkAll("Divisible[Equiv]", SerializableTests.serializable(Divisible[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 36b2aac5ed..7ad0c79b6e 100644 --- a/tests/src/test/scala/cats/tests/FunctionSuite.scala +++ b/tests/src/test/scala/cats/tests/FunctionSuite.scala @@ -101,7 +101,7 @@ 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) - checkAll("Function1[?, Monoid]", DivisibleTests[Function1[?, Long]].divisible[Int, Int, Int]) + 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])) @@ -113,5 +113,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("Divisible[Function1[?, Monoid]]", SerializableTests.serializable(Divisible[? => Long])) + 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 59161dabbd..770d753599 100644 --- a/tests/src/test/scala/cats/tests/IdTSuite.scala +++ b/tests/src/test/scala/cats/tests/IdTSuite.scala @@ -46,8 +46,10 @@ class IdTSuite extends CatsSuite { } { - checkAll("IdT[Const[String, ?], ?]", DivisibleTests[IdT[Const[String, ?], ?]].divisible[Int, Int, Int]) - checkAll("Divisible[IdT[Const[String, ?], ?]]", SerializableTests.serializable(Divisible[IdT[Const[String, ?], ?]])) + checkAll("IdT[Const[String, ?], ?]", + ContravariantMonoidalTests[IdT[Const[String, ?], ?]].contravariantMonoidal[Int, Int, Int]) + checkAll("ContravariantMonoidal[IdT[Const[String, ?], ?]]", + SerializableTests.serializable(ContravariantMonoidal[IdT[Const[String, ?], ?]])) } { diff --git a/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala b/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala index 3419160892..1c5be6d9c6 100644 --- a/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala +++ b/tests/src/test/scala/cats/tests/IndexedStateTSuite.scala @@ -373,10 +373,10 @@ class IndexedStateTSuite extends CatsSuite { } { - // F has a Divisible - val SD = Divisible[StateT[Const[String, ?], String, ?]] + // F has a ContravariantMonoidal + val SD = ContravariantMonoidal[StateT[Const[String, ?], String, ?]] - checkAll("Divisible[StateT[Const[String, ?], String, ?]]", SerializableTests.serializable(SD)) + checkAll("ContravariantMonoidal[StateT[Const[String, ?], String, ?]]", SerializableTests.serializable(SD)) } { diff --git a/tests/src/test/scala/cats/tests/KleisliSuite.scala b/tests/src/test/scala/cats/tests/KleisliSuite.scala index 242b2f3967..d9d8074b8f 100644 --- a/tests/src/test/scala/cats/tests/KleisliSuite.scala +++ b/tests/src/test/scala/cats/tests/KleisliSuite.scala @@ -88,9 +88,11 @@ class KleisliSuite extends CatsSuite { } { - implicit val catsDataDivisibleForKleisli = Kleisli.catsDataDivisibleForKleisli[Const[String, ?], Int] - checkAll("Kleisli[Const[String, ?], Int, Int]", DivisibleTests[Kleisli[Const[String, ?], Int, ?]].divisible[Int, Int, Int]) - checkAll("Divisible[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Divisible[Kleisli[Const[String, ?], 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, ?]])) } { diff --git a/tests/src/test/scala/cats/tests/NestedSuite.scala b/tests/src/test/scala/cats/tests/NestedSuite.scala index dd966b2b34..616022d671 100644 --- a/tests/src/test/scala/cats/tests/NestedSuite.scala +++ b/tests/src/test/scala/cats/tests/NestedSuite.scala @@ -51,9 +51,11 @@ class NestedSuite extends CatsSuite { } { - // Applicative + Divisible functor composition - checkAll("Nested[Option, Const[String, ?], ?]", DivisibleTests[Nested[Option, Const[String, ?], ?]].divisible[Int, Int, Int]) - checkAll("Divisible[Nested[Option, Const[String, ?], ?]", SerializableTests.serializable(Divisible[Nested[Option, Const[String, ?], ?]])) + // 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, ?], ?]])) } { diff --git a/tests/src/test/scala/cats/tests/OptionTSuite.scala b/tests/src/test/scala/cats/tests/OptionTSuite.scala index 271d2707f2..d3b7eea447 100644 --- a/tests/src/test/scala/cats/tests/OptionTSuite.scala +++ b/tests/src/test/scala/cats/tests/OptionTSuite.scala @@ -45,9 +45,10 @@ class OptionTSuite extends CatsSuite { { - // F has a Divisible - checkAll("OptionT[Const[String, ?], Int]", DivisibleTests[OptionT[Const[String, ?], ?]].divisible[Int, Int, Int]) - checkAll("Divisible[OptionT[Const[String, ?], Int]]", SerializableTests.serializable(Divisible[OptionT[Const[String, ?], ?]])) + // 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, ?], ?]])) } { diff --git a/tests/src/test/scala/cats/tests/OrderSuite.scala b/tests/src/test/scala/cats/tests/OrderSuite.scala index 064f548c9c..adb5857d78 100644 --- a/tests/src/test/scala/cats/tests/OrderSuite.scala +++ b/tests/src/test/scala/cats/tests/OrderSuite.scala @@ -8,7 +8,7 @@ class OrderSuite extends CatsSuite { { Invariant[Order] Contravariant[Order] - Divisible[Order] + ContravariantMonoidal[Order] } checkAll("Int", OrderTests[Int].order) @@ -22,7 +22,7 @@ object OrderSuite { import cats.instances.order._ Invariant[Order] Contravariant[Order] - Divisible[Order] + ContravariantMonoidal[Order] () } diff --git a/tests/src/test/scala/cats/tests/OrderingSuite.scala b/tests/src/test/scala/cats/tests/OrderingSuite.scala index ffdc3e88f8..42e8323de5 100644 --- a/tests/src/test/scala/cats/tests/OrderingSuite.scala +++ b/tests/src/test/scala/cats/tests/OrderingSuite.scala @@ -12,10 +12,12 @@ class OrderingSuite extends CatsSuite { Contravariant[Ordering] Semigroupal[Ordering] ContravariantSemigroupal[Ordering] - Divisible[Ordering] + ContravariantMonoidal[Ordering] checkAll("Contravariant[Ordering]", ContravariantTests[Ordering].contravariant[Int, Int, Int]) checkAll("Semigroupal[Ordering]", SemigroupalTests[Ordering].semigroupal[Int, Int, Int]) - checkAll("Divisible[Ordering]", DivisibleTests[Ordering].divisible[Int, Int, Int]) - checkAll("Divisible[Ordering]", SerializableTests.serializable(Divisible[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 3ab0d4b72e..822de76984 100644 --- a/tests/src/test/scala/cats/tests/Tuple2KSuite.scala +++ b/tests/src/test/scala/cats/tests/Tuple2KSuite.scala @@ -20,8 +20,10 @@ 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]", DivisibleTests[λ[α => Tuple2K[Const[String, ?], Const[Int, ?], α]]].divisible[Int, Int, Int]) - checkAll("Divisible[Tuple2K[Const[String, ?], Const[Int, ?], Int]]", SerializableTests.serializable(Divisible[λ[α => Tuple2K[Const[String, ?], Const[Int, ?], α]]])) + 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 80cbc163fd..687c0d9e86 100644 --- a/tests/src/test/scala/cats/tests/WriterTSuite.scala +++ b/tests/src/test/scala/cats/tests/WriterTSuite.scala @@ -363,8 +363,8 @@ class WriterTSuite extends CatsSuite { } { - // F has a Divisible - Divisible[WriterT[Const[String, ?], Int, ?]] + // F has a ContravariantMonoidal + ContravariantMonoidal[WriterT[Const[String, ?], Int, ?]] } checkAll("WriterT[Option, Int, ?]", CommutativeMonadTests[WriterT[Option, Int, ?]].commutativeMonad[Int, Int, Int]) From afd8aef7b76a79e34df57036abff89cf8a853250 Mon Sep 17 00:00:00 2001 From: LukaJCB Date: Sat, 25 Nov 2017 09:24:52 +0100 Subject: [PATCH 03/14] Make scalastyle happy --- core/src/main/scala/cats/Composed.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Composed.scala b/core/src/main/scala/cats/Composed.scala index 918e6969e7..0a6953e7be 100644 --- a/core/src/main/scala/cats/Composed.scala +++ b/core/src/main/scala/cats/Composed.scala @@ -119,11 +119,11 @@ private[cats] trait ComposedApplicativeContravariantMonoidal[F[_], G[_]] extends override def unit[A]: F[G[A]] = F.pure(G.unit) - override def contramap[A, B](fa: F[G[A]])(f: B => A) = + 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.map2(fa, fb)(G.product(_,_)) + 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 => From d03cfd5cb0ed82504c0dcbef8f5d93e0c27f452d Mon Sep 17 00:00:00 2001 From: LukaJCB Date: Sat, 25 Nov 2017 10:00:31 +0100 Subject: [PATCH 04/14] Add mima exceptions --- build.sbt | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/build.sbt b/build.sbt index 6a1b4bce06..477af16612 100644 --- a/build.sbt +++ b/build.sbt @@ -198,6 +198,35 @@ def mimaSettings(moduleName: String) = Seq( exclude[MissingTypesProblem]("cats.data.OneAndLowPriority3"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority2"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority1"), + 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[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[DirectMissingMethodProblem]("cats.data.OneAndLowPriority3.catsDataNonEmptyTraverseForOneAnd"), exclude[DirectMissingMethodProblem]("cats.data.OneAndLowPriority2.catsDataTraverseForOneAnd"), exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdNonEmptyParallelForZipVector"), From cdf573298659e92dbbd7230e676f40f1e4a41c1d Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Sun, 26 Nov 2017 21:27:26 -0800 Subject: [PATCH 05/14] Use SemigroupalArityFunctions and TupleSemigroupal --- core/src/main/scala/cats/ContravariantMonoidal.scala | 9 +++++---- .../main/scala/cats/ContravariantSemigroupal.scala | 5 ++--- core/src/main/scala/cats/data/IndexedStateT.scala | 6 +++--- .../scala/cats/syntax/contravariantMonoidal.scala | 11 ++++++++++- .../scala/cats/syntax/contravariantSemigroupal.scala | 10 +++++++++- .../scala/cats/laws/ContravariantMonoidalLaws.scala | 10 +++++----- 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/core/src/main/scala/cats/ContravariantMonoidal.scala b/core/src/main/scala/cats/ContravariantMonoidal.scala index b20e8d139b..244acdc937 100644 --- a/core/src/main/scala/cats/ContravariantMonoidal.scala +++ b/core/src/main/scala/cats/ContravariantMonoidal.scala @@ -3,7 +3,8 @@ package cats import simulacrum.typeclass /** - * ContravariantMonoidal functors + * [[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. * @@ -11,7 +12,6 @@ import simulacrum.typeclass * 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 @@ -20,7 +20,7 @@ import simulacrum.typeclass def unit[A]: F[A] def liftContravariant[A, B](f: A => B): F[B] => F[A] = - contramap2(unit, _: F[B])(((b: B) => (b, b)) compose f) + ContravariantMonoidal.contramap2(unit[B], _: F[B])(((b: B) => (b, b)) compose f)(self, self) // Technically, this is not correct, as the Applicative is composed with the ContravariantMonoidal, not the other way around def composeApplicative[G[_]: Applicative]: ContravariantMonoidal[λ[α => G[F[α]]]] = @@ -29,4 +29,5 @@ import simulacrum.typeclass val G = self } } - +object ContravariantMonoidal extends SemigroupalArityFunctions { +} diff --git a/core/src/main/scala/cats/ContravariantSemigroupal.scala b/core/src/main/scala/cats/ContravariantSemigroupal.scala index 0655df4e09..a1800032c0 100644 --- a/core/src/main/scala/cats/ContravariantSemigroupal.scala +++ b/core/src/main/scala/cats/ContravariantSemigroupal.scala @@ -12,7 +12,6 @@ import simulacrum.typeclass def F = self def G = Functor[G] } - - def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] = - contramap(product(fb, fc))(f) +} +object ContravariantSemigroupal extends SemigroupalArityFunctions { } diff --git a/core/src/main/scala/cats/data/IndexedStateT.scala b/core/src/main/scala/cats/data/IndexedStateT.scala index b57001f993..5bef3ed408 100644 --- a/core/src/main/scala/cats/data/IndexedStateT.scala +++ b/core/src/main/scala/cats/data/IndexedStateT.scala @@ -379,13 +379,13 @@ private[data] sealed abstract class IndexedStateTContravariantMonoidal[F[_], S] 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) - override 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] = + 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) => - F.contramap2(G.map(fb.runF)(_.apply(s)), G.map(fc.runF)(_.apply(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, ?]] { diff --git a/core/src/main/scala/cats/syntax/contravariantMonoidal.scala b/core/src/main/scala/cats/syntax/contravariantMonoidal.scala index db1d73c646..2e4f62a5c3 100644 --- a/core/src/main/scala/cats/syntax/contravariantMonoidal.scala +++ b/core/src/main/scala/cats/syntax/contravariantMonoidal.scala @@ -3,4 +3,13 @@ package syntax import cats.ContravariantMonoidal -trait ContravariantMonoidalSyntax extends ContravariantMonoidal.ToContravariantMonoidalOps +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 index 5dc6f12e0a..1c583d1467 100644 --- a/core/src/main/scala/cats/syntax/contravariantSemigroupal.scala +++ b/core/src/main/scala/cats/syntax/contravariantSemigroupal.scala @@ -3,4 +3,12 @@ package syntax import cats.ContravariantSemigroupal -trait ContravariantSemigroupalSyntax extends ContravariantSemigroupal.ToContravariantSemigroupalOps +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/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala b/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala index 29a8f8f80c..29a9890090 100644 --- a/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala +++ b/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala @@ -17,19 +17,19 @@ trait ContravariantMonoidalLaws[F[_]] extends ContravariantLaws[F] { def delta[A](a: A): (A, A) = (a, a) def contravariantMonoidalUnitRight[A](fa: F[A]): IsEq[F[A]] = - fa.contramap2(F.unit)(delta[A]) <-> fa + (fa, F.unit[A]).contramapN(delta[A]) <-> fa def contravariantMonoidalUnitLeft[A](fa: F[A]): IsEq[F[A]] = - (F.unit).contramap2(fa)(delta[A]) <-> fa + (F.unit[A], fa).contramapN(delta[A]) <-> fa def contravariantMonoidalContramap2CompatibleContramapLeft[A, B, C](fa: F[A], f: B => (A, C)): IsEq[F[B]] = - fa.contramap2(F.unit)(f) <-> fa.contramap(f andThen (_._1)) + (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).contramap2(fa)(f) <-> fa.contramap(f andThen (_._2)) + (F.unit[B], fa).contramapN(f) <-> fa.contramap(f andThen (_._2)) def contravariantMonoidalContramap2DiagonalAssociates[A](m: F[A], n: F[A], o: F[A]): IsEq[F[A]] = - (m.contramap2(n)(delta[A])).contramap2(o)(delta[A]) <-> m.contramap2(n.contramap2(o)(delta[A]))(delta[A]) + ((m, n).contramapN(delta[A]), o).contramapN(delta[A]) <-> (m, (n, o).contramapN(delta[A])).contramapN(delta[A]) } object ContravariantMonoidalLaws { From caf2a4037232fd199c1927f5ca62ded7ec75f5bd Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Sun, 26 Nov 2017 22:11:51 -0800 Subject: [PATCH 06/14] Clean up `ContravariantMonoidal` documentation --- .../main/resources/microsite/data/menu.yml | 4 +- .../tut/typeclasses/contravariantmonoidal.md | 110 ++++++++++++++++++ .../typeclasses/contravariantsemigroupal.md | 80 ------------- 3 files changed, 112 insertions(+), 82 deletions(-) create mode 100644 docs/src/main/tut/typeclasses/contravariantmonoidal.md delete mode 100644 docs/src/main/tut/typeclasses/contravariantsemigroupal.md diff --git a/docs/src/main/resources/microsite/data/menu.yml b/docs/src/main/resources/microsite/data/menu.yml index 5bb8704986..c3ef18d054 100644 --- a/docs/src/main/resources/microsite/data/menu.yml +++ b/docs/src/main/resources/microsite/data/menu.yml @@ -69,8 +69,8 @@ options: - title: Contravariant url: typeclasses/contravariant.html menu_section: variance - - title: ContravariantSemigroupal - url: typeclasses/contravariantsemigroupal.html + - title: ContravariantMonoidal + url: typeclasses/contravariantmonoidal.html menu_type: typeclasses - title: Invariant url: typeclasses/invariant.html diff --git a/docs/src/main/tut/typeclasses/contravariantmonoidal.md b/docs/src/main/tut/typeclasses/contravariantmonoidal.md new file mode 100644 index 0000000000..11aa0b25fb --- /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:book: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 +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/docs/src/main/tut/typeclasses/contravariantsemigroupal.md b/docs/src/main/tut/typeclasses/contravariantsemigroupal.md deleted file mode 100644 index f860ae4b8e..0000000000 --- a/docs/src/main/tut/typeclasses/contravariantsemigroupal.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -layout: docs -title: "Contravariant Semigroupal" -section: "typeclasses" -source: "core/src/main/scala/cats/ContravariantSemigroupal.scala" -scaladoc: "#cats.ContravariantSemigroupal" ---- -# Contravariant Semigroupal - -The `ContravariantSemigroupal` type class is for contravariant functors that can define a -`contramap2` function that looks like: - -```scala -def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] -``` - -This is similar to the `map2` function on the `Apply` 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 `ContravariantSemigroupal` - -An example application would be the case of predicates. Consider the type, - -```tut:book:silent -import cats._ - -import cats.implicits._ - -case class Predicate[A](run: A => Boolean) -``` - -Then, we can exhibit a `ContravariantSemigroupal` for `Predicate` by basing it on the -`Monoid` for `Boolean` via `&&` as, - -```tut:book:silent -implicit val contravariantSemigroupalPredicate: ContravariantSemigroupal[Predicate] = - new ContravariantMonoidal [Predicate] { - def unit[A]: Predicate[A] = Predicate[A](Function.const(true)) - - def contramap2[A, B, C](fb: Predicate[B], fc: Predicate[C] - )(f: A => (B, C)): Predicate [A] = Predicate( - (a: A) => f(a) match { - case (b, c) => fb.run(b) && fc.run(c) - }) - } -``` - -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)) -``` - -More interestingly, we can combine two predicates using -a `contramap2`. - -```tut:book -case class Transaction(value: Money, payee: String) - -def isEvan: Predicate[String] = Predicate(_ == "Evan") - -def isEvenPaymentToEvan = - isEvenMoney.contramap2(isEvan)( - (trans: Transaction) => (trans.value, trans.payee)) - -isEvenPaymentToEvan.run(Transaction(Money(56), "Evan")) -``` From 81e7f062c7c367721dc8b3893c7b41b4d242eeb7 Mon Sep 17 00:00:00 2001 From: LukaJCB Date: Mon, 27 Nov 2017 11:20:39 +0100 Subject: [PATCH 07/14] Add missing mima exclusions --- build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sbt b/build.sbt index 477af16612..8984def98d 100644 --- a/build.sbt +++ b/build.sbt @@ -227,6 +227,8 @@ def mimaSettings(moduleName: String) = Seq( 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"), From 2e8c20a4040bedb80e93457d160f81634c1ae861 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Mon, 27 Nov 2017 10:41:48 -0800 Subject: [PATCH 08/14] No `tut` shadowing, remove extraneous braces --- core/src/main/scala/cats/ContravariantMonoidal.scala | 3 +-- core/src/main/scala/cats/ContravariantSemigroupal.scala | 3 +-- docs/src/main/tut/typeclasses/contravariantmonoidal.md | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/ContravariantMonoidal.scala b/core/src/main/scala/cats/ContravariantMonoidal.scala index 244acdc937..e650892eeb 100644 --- a/core/src/main/scala/cats/ContravariantMonoidal.scala +++ b/core/src/main/scala/cats/ContravariantMonoidal.scala @@ -29,5 +29,4 @@ import simulacrum.typeclass val G = self } } -object ContravariantMonoidal extends SemigroupalArityFunctions { -} +object ContravariantMonoidal extends SemigroupalArityFunctions diff --git a/core/src/main/scala/cats/ContravariantSemigroupal.scala b/core/src/main/scala/cats/ContravariantSemigroupal.scala index a1800032c0..d641fe9067 100644 --- a/core/src/main/scala/cats/ContravariantSemigroupal.scala +++ b/core/src/main/scala/cats/ContravariantSemigroupal.scala @@ -13,5 +13,4 @@ import simulacrum.typeclass def G = Functor[G] } } -object ContravariantSemigroupal extends SemigroupalArityFunctions { -} +object ContravariantSemigroupal extends SemigroupalArityFunctions diff --git a/docs/src/main/tut/typeclasses/contravariantmonoidal.md b/docs/src/main/tut/typeclasses/contravariantmonoidal.md index 11aa0b25fb..df07465894 100644 --- a/docs/src/main/tut/typeclasses/contravariantmonoidal.md +++ b/docs/src/main/tut/typeclasses/contravariantmonoidal.md @@ -10,7 +10,7 @@ scaladoc: "#cats.ContravariantMonoidal" The `ContravariantMonoidal` type class is for [`Contravariant`](contravariant.html) functors that can define a `product` function and a `unit` function. -```tut:book:silent +```scala import cats.Contravariant trait ContravariantMonoidal[F[_]] extends Contravariant[F] { From a27927d22b8d8916715d31835e3a096c79a00b32 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Mon, 27 Nov 2017 13:31:28 -0800 Subject: [PATCH 09/14] Increase WriterT coverage, tut:reset --- docs/src/main/tut/typeclasses/contravariantmonoidal.md | 2 +- tests/src/test/scala/cats/tests/WriterTSuite.scala | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/typeclasses/contravariantmonoidal.md b/docs/src/main/tut/typeclasses/contravariantmonoidal.md index df07465894..06d0e575fc 100644 --- a/docs/src/main/tut/typeclasses/contravariantmonoidal.md +++ b/docs/src/main/tut/typeclasses/contravariantmonoidal.md @@ -10,7 +10,7 @@ scaladoc: "#cats.ContravariantMonoidal" The `ContravariantMonoidal` type class is for [`Contravariant`](contravariant.html) functors that can define a `product` function and a `unit` function. -```scala +```tut:reset import cats.Contravariant trait ContravariantMonoidal[F[_]] extends Contravariant[F] { diff --git a/tests/src/test/scala/cats/tests/WriterTSuite.scala b/tests/src/test/scala/cats/tests/WriterTSuite.scala index 687c0d9e86..f1e0329da2 100644 --- a/tests/src/test/scala/cats/tests/WriterTSuite.scala +++ b/tests/src/test/scala/cats/tests/WriterTSuite.scala @@ -363,8 +363,11 @@ class WriterTSuite extends CatsSuite { } { - // F has a ContravariantMonoidal + // 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]) From 9b09aa4424aca01b1c97a409e0dd719acd4a9453 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Mon, 27 Nov 2017 14:41:06 -0800 Subject: [PATCH 10/14] Use tut:reset correctly --- docs/src/main/tut/typeclasses/contravariantmonoidal.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/typeclasses/contravariantmonoidal.md b/docs/src/main/tut/typeclasses/contravariantmonoidal.md index 06d0e575fc..ddcd56cfa1 100644 --- a/docs/src/main/tut/typeclasses/contravariantmonoidal.md +++ b/docs/src/main/tut/typeclasses/contravariantmonoidal.md @@ -10,7 +10,7 @@ scaladoc: "#cats.ContravariantMonoidal" The `ContravariantMonoidal` type class is for [`Contravariant`](contravariant.html) functors that can define a `product` function and a `unit` function. -```tut:reset +```tut:silent import cats.Contravariant trait ContravariantMonoidal[F[_]] extends Contravariant[F] { @@ -38,7 +38,7 @@ but there are also interesting instances for other types. An example application would be the case of predicates. Consider the type, -```tut:book:silent +```tut:book:silent:reset import cats._ import cats.implicits._ From 1c14af2e33635ad09c4313f0ea56dcd55e9052ad Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Tue, 28 Nov 2017 14:35:05 -0800 Subject: [PATCH 11/14] Move composition functions into Applicative --- core/src/main/scala/cats/Applicative.scala | 6 ++++++ core/src/main/scala/cats/ContravariantMonoidal.scala | 7 ------- core/src/main/scala/cats/data/Nested.scala | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) 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/ContravariantMonoidal.scala b/core/src/main/scala/cats/ContravariantMonoidal.scala index e650892eeb..be75400dfd 100644 --- a/core/src/main/scala/cats/ContravariantMonoidal.scala +++ b/core/src/main/scala/cats/ContravariantMonoidal.scala @@ -21,12 +21,5 @@ import simulacrum.typeclass def liftContravariant[A, B](f: A => B): F[B] => F[A] = ContravariantMonoidal.contramap2(unit[B], _: F[B])(((b: B) => (b, b)) compose f)(self, self) - - // Technically, this is not correct, as the Applicative is composed with the ContravariantMonoidal, not the other way around - def composeApplicative[G[_]: Applicative]: ContravariantMonoidal[λ[α => G[F[α]]]] = - new ComposedApplicativeContravariantMonoidal[G, F] { - val F = Applicative[G] - val G = self - } } object ContravariantMonoidal extends SemigroupalArityFunctions diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index 21b906d346..728688d759 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -65,7 +65,7 @@ private[data] sealed abstract class NestedInstances1 extends NestedInstances2 { implicit def catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal]: ContravariantMonoidal[Nested[F, G, ?]] = new NestedContravariantMonoidal[F, G] { - val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = ContravariantMonoidal[G].composeApplicative[F] + val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = Applicative[F].composeContravariantMonoidal[G] } } From 110ac5ee756db0b8a6b9866086bf0d8432829a32 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Tue, 28 Nov 2017 15:11:27 -0800 Subject: [PATCH 12/14] Reorder instances to be more standard --- build.sbt | 2 ++ core/src/main/scala/cats/data/Const.scala | 11 +++++------ core/src/main/scala/cats/data/Kleisli.scala | 16 ++++++++-------- core/src/main/scala/cats/data/Nested.scala | 10 +++++----- core/src/main/scala/cats/data/Tuple2K.scala | 8 ++++---- .../main/scala/cats/instances/function.scala | 18 ++++++++++-------- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/build.sbt b/build.sbt index 8984def98d..733cf9bb28 100644 --- a/build.sbt +++ b/build.sbt @@ -206,6 +206,8 @@ def mimaSettings(moduleName: String) = Seq( 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_="), diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index a4695594ca..b076870bdf 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -65,11 +65,6 @@ 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] = - fa.retag[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] = @@ -110,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/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index f9fbc5bfb3..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,14 +157,17 @@ 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 { implicit def catsDataAlternativeForKleisli[F[_], A](implicit F0: Alternative[F]): Alternative[Kleisli[F, A, ?]] = new KleisliAlternative[F, A] { def F: Alternative[F] = F0 } - - 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 KleisliInstances3 extends KleisliInstances4 { diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index 728688d759..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 { @@ -62,11 +67,6 @@ private[data] sealed abstract class NestedInstances1 extends NestedInstances2 { new NestedFunctor[F, G] { val FG: Functor[λ[α => F[G[α]]]] = Contravariant[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 NestedInstances2 extends NestedInstances3 { diff --git a/core/src/main/scala/cats/data/Tuple2K.scala b/core/src/main/scala/cats/data/Tuple2K.scala index ad0c9a5907..dbed5a0283 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -29,10 +29,6 @@ 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 @@ -45,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) diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 4dfc3a0ba4..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,13 +35,7 @@ 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) @@ -100,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) + } +} From f7c4104a456fb1919a85f86680e4890b60ee4c2f Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Wed, 29 Nov 2017 14:14:36 -0800 Subject: [PATCH 13/14] Tighten test code for Contravariant derivatives --- .../cats/laws/ContravariantMonoidalLaws.scala | 10 +---- .../laws/ContravariantSemigroupalLaws.scala | 21 ++++++++++ .../ContravariantMonoidalTests.scala | 38 +++++++++-------- .../ContravariantSemigroupalTests.scala | 41 +++++++++++++++++++ .../test/scala/cats/tests/FunctionSuite.scala | 3 +- 5 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/ContravariantSemigroupalLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ContravariantSemigroupalTests.scala diff --git a/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala b/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala index 29a9890090..07a1cb24c6 100644 --- a/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala +++ b/laws/src/main/scala/cats/laws/ContravariantMonoidalLaws.scala @@ -8,14 +8,9 @@ import cats.syntax.contravariantSemigroupal._ /** * Laws that must hold for any `cats.ContravariantMonoidal`. */ -trait ContravariantMonoidalLaws[F[_]] extends ContravariantLaws[F] { +trait ContravariantMonoidalLaws[F[_]] extends ContravariantSemigroupalLaws[F] { implicit override def F: ContravariantMonoidal[F] - /** - * The traditional diagonal map - */ - def delta[A](a: A): (A, A) = (a, a) - def contravariantMonoidalUnitRight[A](fa: F[A]): IsEq[F[A]] = (fa, F.unit[A]).contramapN(delta[A]) <-> fa @@ -27,9 +22,6 @@ trait ContravariantMonoidalLaws[F[_]] extends ContravariantLaws[F] { 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)) - - def contravariantMonoidalContramap2DiagonalAssociates[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 ContravariantMonoidalLaws { 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 index bf4e5bbe7f..21b30dc860 100644 --- a/laws/src/main/scala/cats/laws/discipline/ContravariantMonoidalTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ContravariantMonoidalTests.scala @@ -3,35 +3,41 @@ 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 ContravariantTests[F] { +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]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { - new DefaultRuleSet( - name = "contravariantMonoidal", - parent = Some(contravariant[A, B, C]), - "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] _), - "contravariantMonoidal contramap2 delta associates" -> - forAll(laws.contravariantMonoidalContramap2DiagonalAssociates[A] _) - ) + 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] _) + ) + } } } 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/FunctionSuite.scala b/tests/src/test/scala/cats/tests/FunctionSuite.scala index 7ad0c79b6e..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,8 @@ 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 From 6e7a1b58440c98901caee9f9abe04e308123554d Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Thu, 30 Nov 2017 17:41:03 -0800 Subject: [PATCH 14/14] Remove extra allocations, use `on` and `by` --- build.sbt | 1 + core/src/main/scala/cats/Contravariant.scala | 2 ++ .../main/scala/cats/ContravariantMonoidal.scala | 3 --- core/src/main/scala/cats/instances/eq.scala | 12 +++--------- core/src/main/scala/cats/instances/equiv.scala | 6 +----- core/src/main/scala/cats/instances/order.scala | 16 +++++----------- .../src/main/scala/cats/instances/ordering.scala | 13 ++++--------- 7 files changed, 16 insertions(+), 37 deletions(-) diff --git a/build.sbt b/build.sbt index 733cf9bb28..368fe83509 100644 --- a/build.sbt +++ b/build.sbt @@ -198,6 +198,7 @@ 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"), 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 index be75400dfd..f4f6881436 100644 --- a/core/src/main/scala/cats/ContravariantMonoidal.scala +++ b/core/src/main/scala/cats/ContravariantMonoidal.scala @@ -18,8 +18,5 @@ import simulacrum.typeclass * the diagonal */ def unit[A]: F[A] - - def liftContravariant[A, B](f: A => B): F[B] => F[A] = - ContravariantMonoidal.contramap2(unit[B], _: F[B])(((b: B) => (b, b)) compose f)(self, self) } object ContravariantMonoidal extends SemigroupalArityFunctions diff --git a/core/src/main/scala/cats/instances/eq.scala b/core/src/main/scala/cats/instances/eq.scala index 80770ad05f..dc304686d5 100644 --- a/core/src/main/scala/cats/instances/eq.scala +++ b/core/src/main/scala/cats/instances/eq.scala @@ -15,17 +15,11 @@ trait EqInstances { * 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.instance { (l: B, r: B) => - fa.eqv(f(l), f(r)) - } + Eq.by(f)(fa) def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] = - Eq.instance { (l, r) => - (l, r) match { - case ((aL, bL), (aR, bR)) => - fa.eqv(aL, aR) && - fb.eqv(bL, bR) - } + 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 75880fed0f..012d189ff7 100644 --- a/core/src/main/scala/cats/instances/equiv.scala +++ b/core/src/main/scala/cats/instances/equiv.scala @@ -25,11 +25,7 @@ trait EquivInstances { def product[A, B](fa: Equiv[A], fb: Equiv[B]): Equiv[(A, B)] = new Equiv[(A, B)] { def equiv(l: (A, B), r: (A, B)): Boolean = - (l, r) match { - case ((aL, bL), (aR, bR)) => - fa.equiv(aL, aR) && - fb.equiv(bR, bL) - } + fa.equiv(l._1, r._1) && fb.equiv(l._2, r._2) } } } diff --git a/core/src/main/scala/cats/instances/order.scala b/core/src/main/scala/cats/instances/order.scala index 79d322fc3a..8a239d264c 100644 --- a/core/src/main/scala/cats/instances/order.scala +++ b/core/src/main/scala/cats/instances/order.scala @@ -13,20 +13,14 @@ trait OrderInstances extends cats.kernel.OrderToOrderingConversion { * 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] = - new Order[B] { - def compare(x: B, y: B): Int = - fa.compare(f(x), f(y)) - } + Order.by(f)(fa) def product[A, B](fa: Order[A], fb: Order[B]): Order[(A, B)] = new Order[(A, B)] { - def compare(x: (A, B), y: (A, B)): Int = - (x, y) match { - case ((aL, bL), (aR, bR)) => { - val z = fa.compare(aL, aR) - if (z == 0) fb.compare(bL, bR) else z - } - } + def compare(x: (A, B), y: (A, B)): Int = { + val z = fa.compare(x._1, y._1) + if (z == 0) fb.compare(x._2, y._2) else z + } } } } diff --git a/core/src/main/scala/cats/instances/ordering.scala b/core/src/main/scala/cats/instances/ordering.scala index 28850e3a9d..5afd3b58e7 100644 --- a/core/src/main/scala/cats/instances/ordering.scala +++ b/core/src/main/scala/cats/instances/ordering.scala @@ -12,18 +12,13 @@ trait OrderingInstances { def compare(l: A, r: A): Int = 0 } - def contramap[A, B](fa: Ordering[A])(f: B => A): Ordering[B] = - new Ordering[B] { - def compare(x: B, y: B) = fa.compare(f(x), f(y)) - } + 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)] = new Ordering[(A, B)] { - def compare(x: (A, B), y: (A, B)): Int = (x, y) match { - case ((aL, bL), (aR, bR)) => { - val z = fa.compare(aL, aR) - if (z == 0) fb.compare(bL, bR) else z - } + def compare(x: (A, B), y: (A, B)): Int = { + val z = fa.compare(x._1, y._1) + if (z == 0) fb.compare(x._2, y._2) else z } } }