diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01c2b75824..906bbf7301 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,8 +105,8 @@ To build Cats you should have * `console`: launch a REPL * `test`: run the tests * `unidoc`: generate the documentation - * `fmt`: run formatting of the code - * `validate`: run tests, style-checker, and doc generation + * `scalafmtAll`: run formatting of the code + * `prePR`: run tests, style-checker, and doc generation #### Scala and Scala.js diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 4b6579a76f..e74c403d27 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -215,6 +215,12 @@ trait Applicative[F[_]] extends Apply[F] with InvariantMonoidal[F] { self => val G = ContravariantMonoidal[G] } + def composeDecidable[G[_]: Decidable]: Decidable[λ[α => F[G[α]]]] = + new ComposedApplicativeDecidable[F, G] { + val F = self + val G = Decidable[G] + } + /** * Returns the given argument (mapped to Unit) if `cond` is `false`, * otherwise, unit lifted into F. diff --git a/core/src/main/scala/cats/Composed.scala b/core/src/main/scala/cats/Composed.scala index 6f9340c20d..227e83f851 100644 --- a/core/src/main/scala/cats/Composed.scala +++ b/core/src/main/scala/cats/Composed.scala @@ -176,6 +176,18 @@ private[cats] trait ComposedContravariantCovariant[F[_], G[_]] extends Contravar F.contramap(fga)(gb => G.map(gb)(f)) } +private[cats] trait ComposedApplicativeDecidable[F[_], G[_]] + extends Decidable[λ[α => F[G[α]]]] + with ComposedApplicativeContravariantMonoidal[F, G] { outer => + def F: Applicative[F] + def G: Decidable[G] + + def sum[A, B](fa: F[G[A]], fb: F[G[B]]): F[G[Either[A, B]]] = + F.map2(fa, fb)(G.sum) + + override lazy val zero: F[G[Nothing]] = F.pure(G.zero) +} + private[cats] trait ComposedApplicativeContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[λ[α => F[G[α]]]] { outer => def F: Applicative[F] diff --git a/core/src/main/scala/cats/ContravariantMonoidal.scala b/core/src/main/scala/cats/ContravariantMonoidal.scala index d534fe34bb..205655002c 100644 --- a/core/src/main/scala/cats/ContravariantMonoidal.scala +++ b/core/src/main/scala/cats/ContravariantMonoidal.scala @@ -38,7 +38,6 @@ trait ContravariantMonoidal[F[_]] extends ContravariantSemigroupal[F] with Invar * the diagonal */ def trivial[A]: F[A] = contramap(unit)(_ => ()) - } object ContravariantMonoidal extends SemigroupalArityFunctions { def monoid[F[_], A](implicit f: ContravariantMonoidal[F]): Monoid[F[A]] = diff --git a/core/src/main/scala/cats/Decidable.scala b/core/src/main/scala/cats/Decidable.scala new file mode 100644 index 0000000000..14c5bb4b29 --- /dev/null +++ b/core/src/main/scala/cats/Decidable.scala @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package cats + +/** + * [[Decidable]] functors are functors that supply + * a `decide` operation allowing choices to be made. + * + * This is comparable to [[Alternative]] in the + * covariant case. + * + * Must obey laws in `cats.laws.DecidableLaws`. + * + * Based on ekmett's contravariant library: + * [[https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html#g:2]] + */ +trait Decidable[F[_]] extends ContravariantMonoidal[F] { + def sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] + def zero: F[Nothing] + + def decide[A, B, C](fa: F[A], fb: F[B])(f: C => Either[A, B]): F[C] = + contramap(sum(fa, fb))(f) + + def lose[A](f: A => Nothing): F[A] = contramap[Nothing, A](zero)(f) +} + +object Decidable { + + /** + * Summon an instance of [[Decidable]] for `F`. + */ + @inline def apply[F[_]](implicit instance: Decidable[F]): Decidable[F] = instance + + trait Ops[F[_], A] extends Serializable { + type TypeClassType <: Decidable[F] + def self: F[A] + val typeClassInstance: TypeClassType + def sum[B](fb: F[B]): F[Either[A, B]] = typeClassInstance.sum[A, B](self, fb) + def decide[B, C](fb: F[B])(f: C => Either[A, B]): F[C] = typeClassInstance.decide[A, B, C](self, fb)(f) + } + trait AllOps[F[_], A] extends Ops[F, A] with ContravariantMonoidal.AllOps[F, A] { + type TypeClassType <: Decidable[F] + } + trait ToDecidableOps extends Serializable { + implicit def toDecidableOps[F[_], A](target: F[A])(implicit tc: Decidable[F]): Ops[F, A] { + type TypeClassType = Decidable[F] + } = + new Ops[F, A] { + type TypeClassType = Decidable[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } + } +} diff --git a/core/src/main/scala/cats/Invariant.scala b/core/src/main/scala/cats/Invariant.scala index f0e100c957..30fb3c46f3 100644 --- a/core/src/main/scala/cats/Invariant.scala +++ b/core/src/main/scala/cats/Invariant.scala @@ -176,9 +176,12 @@ object Invariant extends ScalaVersionSpecificInvariantInstances with InvariantIn cats.instances.ordering.catsContravariantMonoidalForOrdering implicit def catsContravariantMonoidalForPartialOrdering: ContravariantMonoidal[PartialOrdering] = cats.instances.partialOrdering.catsContravariantMonoidalForPartialOrdering - implicit def catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] = + + @deprecated("Prefer cats.instances.eq.catsDecidableForEq, which supersedes this instance", "2.9.0") + def catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] = cats.instances.eq.catsContravariantMonoidalForEq - implicit def catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] = + @deprecated("Prefer cats.instances.eq.catsDecidableForEquiv, which supersedes this instance", "2.9.0") + def catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] = cats.instances.equiv.catsContravariantMonoidalForEquiv implicit def catsContravariantForHash: Contravariant[Hash] = cats.instances.all.catsContravariantForHash @@ -195,6 +198,13 @@ object Invariant extends ScalaVersionSpecificInvariantInstances with InvariantIn implicit def catsInvariantForFractional: Invariant[Fractional] = cats.instances.invariant.catsInvariantForFractional + implicit def catsDecidableForEquiv: Decidable[Equiv] = + cats.instances.equiv.catsDecidableForEquiv + implicit def catsDecidableForEq: Decidable[Eq] = + cats.instances.eq.catsDecidableForEq + implicit def catsDecidableForPredicate: Decidable[* => Boolean] = + cats.instances.function.catsStdDecidableForPredicate + implicit val catsInvariantMonoid: Invariant[Monoid] = new Invariant[Monoid] { def imap[A, B](fa: Monoid[A])(f: A => B)(g: B => A): Monoid[B] = diff --git a/core/src/main/scala/cats/Semigroupal.scala b/core/src/main/scala/cats/Semigroupal.scala index 88f7ad15bd..5f4a569be0 100644 --- a/core/src/main/scala/cats/Semigroupal.scala +++ b/core/src/main/scala/cats/Semigroupal.scala @@ -108,15 +108,17 @@ object Semigroupal extends ScalaVersionSpecificSemigroupalInstances with Semigro cats.instances.ordering.catsContravariantMonoidalForOrdering implicit val catsSemigroupalForPartialOrdering: Semigroupal[PartialOrdering] = cats.instances.partialOrdering.catsContravariantMonoidalForPartialOrdering - implicit val catsSemigroupalForEq: Semigroupal[Eq] = cats.instances.eq.catsContravariantMonoidalForEq + implicit val catsSemigroupalForEq: Semigroupal[Eq] = cats.instances.eq.catsDecidableForEq implicit val catsSemigroupalForEquiv: Semigroupal[Equiv] = - cats.instances.equiv.catsContravariantMonoidalForEquiv + cats.instances.equiv.catsDecidableForEquiv implicit val catsSemigroupalForMonoid: Semigroupal[Monoid] = cats.instances.invariant.catsSemigroupalForMonoid implicit val catsSemigroupalForSemigroup: Semigroupal[Semigroup] = cats.instances.invariant.catsInvariantMonoidalSemigroup implicit val catsSemigroupalForCommutativeSemigroup: Semigroupal[CommutativeSemigroup] = cats.instances.invariant.catsInvariantMonoidalCommutativeSemigroup + implicit val catsSemigroupalForPredicate: Semigroupal[* => Boolean] = + cats.instances.function.catsStdDecidableForPredicate /** * Summon an instance of [[Semigroupal]] for `F`. diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 2f54f70d4e..3c48e66923 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -164,7 +164,6 @@ sealed abstract private[data] class ConstInstances extends ConstInstances0 { } sealed abstract private[data] class ConstInstances0 extends ConstInstances1 { - implicit def catsDataContravariantMonoidalForConst[D: Monoid]: ContravariantMonoidal[Const[D, *]] = new ContravariantMonoidal[Const[D, *]] { override def unit = Const.empty[D, Unit] diff --git a/core/src/main/scala/cats/data/IdT.scala b/core/src/main/scala/cats/data/IdT.scala index 9c3da9f1c4..1196a918f7 100644 --- a/core/src/main/scala/cats/data/IdT.scala +++ b/core/src/main/scala/cats/data/IdT.scala @@ -94,6 +94,15 @@ sealed private[data] trait IdTApplicative[F[_]] extends Applicative[IdT[F, *]] w def pure[A](a: A): IdT[F, A] = IdT.pure(a) } +sealed private[data] trait IdTDecidable[F[_]] extends Decidable[IdT[F, *]] with IdTContravariantMonoidal[F] { + implicit val F0: Decidable[F] + + override def sum[A, B](fa: IdT[F, A], fb: IdT[F, B]): IdT[F, Either[A, B]] = + IdT(F0.sum(fa.value, fb.value)) + + override lazy val zero: IdT[F, Nothing] = IdT[F, Nothing](F0.zero) +} + sealed private[data] trait IdTContravariantMonoidal[F[_]] extends ContravariantMonoidal[IdT[F, *]] { implicit val F0: ContravariantMonoidal[F] @@ -186,6 +195,11 @@ sealed abstract private[data] class IdTInstances6 extends IdTInstances7 { sealed abstract private[data] class IdTInstances5 extends IdTInstances6 { implicit def catsDataFunctorForIdT[F[_]](implicit F: Functor[F]): Functor[IdT[F, *]] = new IdTFunctor[F] { implicit val F0: Functor[F] = F } + + implicit def catsDataDecidableForIdT[F[_]](implicit + F: Decidable[F] + ): Decidable[IdT[F, *]] = + new IdTDecidable[F] { implicit val F0: Decidable[F] = F } } sealed abstract private[data] class IdTInstances4 extends IdTInstances5 { diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 68544c53c6..4d2e6939ed 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -371,6 +371,10 @@ sealed abstract private[data] class KleisliInstances0 extends KleisliInstances0_ implicit def F: Monad[F] = F0 } + implicit def catsDataDecidableForKleisli[F[_], A](implicit + F0: Decidable[F] + ): Decidable[Kleisli[F, A, *]] = + new KleisliDecidable[F, A] { def F: Decidable[F] = F0 } } sealed abstract private[data] class KleisliInstances0_5 extends KleisliInstances1 { @@ -621,6 +625,18 @@ private[data] trait KleisliAlternative[F[_], A] implicit def F: Alternative[F] } +sealed private[data] trait KleisliDecidable[F[_], D] + extends Decidable[Kleisli[F, D, *]] + with KleisliContravariantMonoidal[F, D] { + implicit def F: Decidable[F] + + def sum[A, B](fa: Kleisli[F, D, A], fb: Kleisli[F, D, B]): Kleisli[F, D, Either[A, B]] = + Kleisli(d => F.sum(fa.run(d), fb.run(d))) + + override val zero: Kleisli[F, D, Nothing] = + Kleisli[F, D, Nothing](_ => F.zero) +} + sealed private[data] trait KleisliContravariantMonoidal[F[_], D] extends ContravariantMonoidal[Kleisli[F, D, *]] { implicit def F: ContravariantMonoidal[F] diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index b92399c418..193c128da3 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -65,12 +65,6 @@ sealed abstract private[data] class NestedInstances extends NestedInstances0 { val FG: NonEmptyTraverse[λ[α => F[G[α]]]] = NonEmptyTraverse[F].compose[G] } - implicit def catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal] - : ContravariantMonoidal[Nested[F, G, *]] = - new NestedContravariantMonoidal[F, G] with NestedContravariant[F, G] { - val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = Applicative[F].composeContravariantMonoidal[G] - } - implicit def catsDataDeferForNested[F[_], G[_]](implicit F: Defer[F]): Defer[Nested[F, G, *]] = new Defer[Nested[F, G, *]] { def defer[A](fa: => Nested[F, G, A]): Nested[F, G, A] = @@ -136,6 +130,12 @@ sealed abstract private[data] class NestedInstances1 extends NestedInstances2 { val FG: Reducible[λ[α => F[G[α]]]] = Reducible[F].compose[G] } + implicit def catsDataDecidableForApplicativeForNested[F[_]: Applicative, G[_]: Decidable] + : Decidable[Nested[F, G, *]] = + new NestedDecidable[F, G] with NestedContravariant[F, G] { + val FG: Decidable[λ[α => F[G[α]]]] = Applicative[F].composeDecidable[G] + } + implicit def catsDataFunctorForContravariantForNested[F[_]: Contravariant, G[_]: Contravariant] : Functor[Nested[F, G, *]] = new NestedFunctor[F, G] { @@ -149,6 +149,12 @@ sealed abstract private[data] class NestedInstances2 extends NestedInstances3 { val FG: Foldable[λ[α => F[G[α]]]] = Foldable[F].compose[G] } + implicit def catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal] + : ContravariantMonoidal[Nested[F, G, *]] = + new NestedContravariantMonoidal[F, G] with NestedContravariant[F, G] { + val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = Applicative[F].composeContravariantMonoidal[G] + } + implicit def catsDataContravariantForCovariantNested[F[_]: Contravariant, G[_]: Functor] : Contravariant[Nested[F, G, *]] = new NestedContravariant[F, G] { @@ -394,6 +400,17 @@ private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested Nested(FG.contramap(fga.value)(f)) } +private[data] trait NestedDecidable[F[_], G[_]] + extends Decidable[Nested[F, G, *]] + with NestedContravariantMonoidal[F, G] { + def FG: Decidable[λ[α => F[G[α]]]] + + def sum[A, B](fa: Nested[F, G, A], fb: Nested[F, G, B]): Nested[F, G, Either[A, B]] = + Nested(FG.sum(fa.value, fb.value)) + + lazy val zero: Nested[F, G, Nothing] = Nested[F, G, Nothing](FG.zero) +} + private[data] trait NestedContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[Nested[F, G, *]] { def FG: ContravariantMonoidal[λ[α => F[G[α]]]] diff --git a/core/src/main/scala/cats/data/Op.scala b/core/src/main/scala/cats/data/Op.scala index bb341bc536..184b7ae6ec 100644 --- a/core/src/main/scala/cats/data/Op.scala +++ b/core/src/main/scala/cats/data/Op.scala @@ -41,6 +41,15 @@ sealed abstract private[data] class OpInstances extends OpInstances0 { implicit def catsDataCategoryForOp[Arr[_, _]](implicit ArrC: Category[Arr]): Category[Op[Arr, *, *]] = new OpCategory[Arr] { def Arr: Category[Arr] = ArrC } + implicit def catsDataDecidableForOp[Arr[_, _], R](implicit + ArrC: ArrowChoice[Arr], + monn: Monoid[R] + ): Decidable[Op[Arr, R, *]] = + new OpDecidable[Arr, R] { + def Arr: ArrowChoice[Arr] = ArrC + def M: Monoid[R] = monn + } + implicit def catsDataEqForOp[Arr[_, _], A, B](implicit ArrEq: Eq[Arr[B, A]]): Eq[Op[Arr, A, B]] = new OpEq[Arr, A, B] { def Arr: Eq[Arr[B, A]] = ArrEq } @@ -52,6 +61,17 @@ sealed abstract private[data] class OpInstances extends OpInstances0 { sealed abstract private[data] class OpInstances0 { implicit def catsDataComposeForOp[Arr[_, _]](implicit ArrC: Compose[Arr]): Compose[Op[Arr, *, *]] = new OpCompose[Arr] { def Arr: Compose[Arr] = ArrC } + + implicit def catsDataContravariantMonoidalForOp[Arr[_, _], R](implicit + ArrC: Arrow[Arr], + M0: Monoid[R] + ): ContravariantMonoidal[Op[Arr, R, *]] = + new OpContravariantMonoidal[Arr, R] { def Arr = ArrC; def M = M0 } +} + +sealed abstract private[data] class OpInstances1 { + implicit def catsDataContravariantForOp[Arr[_, _], R](implicit ArrC: Arrow[Arr]): Contravariant[Op[Arr, R, *]] = + new OpContravariant[Arr, R] { def Arr = ArrC } } private[data] trait OpCategory[Arr[_, _]] extends Category[Op[Arr, *, *]] with OpCompose[Arr] { @@ -67,6 +87,39 @@ private[data] trait OpCompose[Arr[_, _]] extends Compose[Op[Arr, *, *]] { f.compose(g) } +private[data] trait OpDecidable[Arr[_, _], R] extends Decidable[Op[Arr, R, *]] with OpContravariantMonoidal[Arr, R] { + implicit def Arr: ArrowChoice[Arr] + implicit def M: Monoid[R] + + def sum[A, B](fa: Op[Arr, R, A], fb: Op[Arr, R, B]): Op[Arr, R, Either[A, B]] = + Op(Arr.choice(fa.run, fb.run)) + + override val zero: Op[Arr, R, Nothing] = + Op[Arr, R, Nothing]( + Arr.lift[Nothing, R]((n: Nothing) => n: R) + ) +} + +private[data] trait OpContravariantMonoidal[Arr[_, _], R] + extends OpContravariant[Arr, R] + with ContravariantMonoidal[Op[Arr, R, *]] { + implicit def Arr: Arrow[Arr] + implicit def M: Monoid[R] + + val unit: Op[Arr, R, Unit] = + Op(Arr.lift(Function.const(M.empty))) + + def product[A, B](fa: Op[Arr, R, A], fb: Op[Arr, R, B]): Op[Arr, R, (A, B)] = + Op(Arr.compose(Arr.lift((M.combine _).tupled), Arr.split(fa.run, fb.run))) +} + +private[data] trait OpContravariant[Arr[_, _], R] extends Contravariant[Op[Arr, R, *]] { + implicit def Arr: Arrow[Arr] + + def contramap[A, B](fa: Op[Arr, R, A])(f: B => A): Op[Arr, R, B] = + Op(Arr.compose(fa.run, Arr.lift(f))) +} + private[data] trait OpEq[Arr[_, _], A, B] extends Eq[Op[Arr, A, B]] { implicit def Arr: Eq[Arr[B, A]] diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 241744c986..ea80cd102d 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -542,7 +542,6 @@ sealed abstract private[data] class OptionTInstances extends OptionTInstances0 { } sealed abstract private[data] class OptionTInstances0 extends OptionTInstances1 { - // the Dummy type is to make this one more specific than catsDataMonadErrorMonadForOptionT on 2.13.x // see https://github.com/typelevel/cats/pull/2335#issuecomment-408249775 implicit def catsDataMonadErrorForOptionT[F[_], E](implicit @@ -671,12 +670,10 @@ private trait OptionTContravariantMonoidal[F[_]] extends ContravariantMonoidal[O 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) - } - ) + F.contramap(F.product(fa.value, fb.value)) { + case Some((x, y)) => (Some(x), Some(y)) + case None => (None, None) + } ) } diff --git a/core/src/main/scala/cats/data/Tuple2K.scala b/core/src/main/scala/cats/data/Tuple2K.scala index 579e2a0e36..08e699192e 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -76,13 +76,13 @@ sealed abstract private[data] class Tuple2KInstances extends Tuple2KInstances0 { def F: Show[F[A]] = FF def G: Show[G[A]] = GF } - implicit def catsDataContravariantMonoidalForTuple2k[F[_], G[_]](implicit - FD: ContravariantMonoidal[F], - GD: ContravariantMonoidal[G] - ): ContravariantMonoidal[λ[α => Tuple2K[F, G, α]]] = - new Tuple2KContravariantMonoidal[F, G] with Tuple2KContravariant[F, G] { - def F: ContravariantMonoidal[F] = FD - def G: ContravariantMonoidal[G] = GD + implicit def catsDataDecidableForTuple2k[F[_], G[_]](implicit + FD: Decidable[F], + GD: Decidable[G] + ): Decidable[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KDecidable[F, G] with Tuple2KContravariant[F, G] { + def F: Decidable[F] = FD + def G: Decidable[G] = GD } implicit def catsDataDeferForTuple2K[F[_], G[_]](implicit F: Defer[F], G: Defer[G]): Defer[Tuple2K[F, G, *]] = @@ -105,14 +105,6 @@ sealed abstract private[data] 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]] = (x, y) => FF.eqv(x.first, y.first) && GG.eqv(x.second, y.second) } @@ -126,6 +118,14 @@ sealed abstract private[data] class Tuple2KInstances1 extends Tuple2KInstances2 def F: Alternative[F] = FF def G: Alternative[G] = GG } + implicit def catsDataContravariantMonoidalForTuple2k[F[_], G[_]](implicit + FD: ContravariantMonoidal[F], + GD: ContravariantMonoidal[G] + ): ContravariantMonoidal[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KContravariantMonoidal[F, G] with Tuple2KContravariant[F, G] { + def F: ContravariantMonoidal[F] = FD + def G: ContravariantMonoidal[G] = GD + } implicit def catsDataFoldableForTuple2K[F[_], G[_]](implicit FF: Foldable[F], GF: Foldable[G] @@ -167,6 +167,14 @@ sealed abstract private[data] class Tuple2KInstances2 extends Tuple2KInstances3 def F: Monad[F] = FM def G: Monad[G] = GM } + 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 catsDataMonoidKForTuple2K[F[_], G[_]](implicit FF: MonoidK[F], GG: MonoidK[G] @@ -298,6 +306,18 @@ sealed private[data] trait Tuple2KContravariant[F[_], G[_]] extends Contravarian Tuple2K(F.contramap(fa.first)(f), G.contramap(fa.second)(f)) } +sealed private[data] trait Tuple2KDecidable[F[_], G[_]] + extends Decidable[λ[α => Tuple2K[F, G, α]]] + with Tuple2KContravariantMonoidal[F, G] { + def F: Decidable[F] + def G: Decidable[G] + + def sum[A, B](fa: Tuple2K[F, G, A], fb: Tuple2K[F, G, B]): Tuple2K[F, G, Either[A, B]] = + Tuple2K(F.sum(fa.first, fb.first), G.sum(fa.second, fb.second)) + + val zero: Tuple2K[F, G, Nothing] = Tuple2K[F, G, Nothing](F.zero, G.zero) +} + sealed private[data] trait Tuple2KContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[λ[α => Tuple2K[F, G, α]]] { def F: ContravariantMonoidal[F] diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 80af484a15..a2ffa574f7 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -514,6 +514,13 @@ sealed abstract private[data] class WriterTInstances7 extends WriterTInstances8 implicit val F0: ApplicativeError[F, E] = F implicit val L0: Monoid[L] = L } + + implicit def catsDataDecidableForWriterT[F[_], L](implicit + F: Decidable[F] + ): Decidable[WriterT[F, L, *]] = + new WriterTDecidable[F, L] { + implicit val F0: Decidable[F] = F + } } sealed abstract private[data] class WriterTInstances8 extends WriterTInstances9 { @@ -746,6 +753,22 @@ sealed private[data] trait WriterTAlternative[F[_], L] implicit override def F0: Alternative[F] } +sealed private[data] trait WriterTDecidable[F[_], L] + extends WriterTContravariantMonoidal[F, L] + with Decidable[WriterT[F, L, *]] { + implicit def F0: Decidable[F] + + override def sum[A, B](fa: WriterT[F, L, A], fb: WriterT[F, L, B]): WriterT[F, L, Either[A, B]] = + WriterT( + F0.decide(fa.run, fb.run) { + case (l, Right(b)) => Right((l, b)) + case (l, Left(a)) => Left((l, a)) + } + ) + + override val zero: WriterT[F, L, Nothing] = WriterT[F, L, Nothing](F0.contramap[Nothing, (L, Nothing)](F0.zero)(_._2)) +} + sealed private[data] trait WriterTContravariantMonoidal[F[_], L] extends ContravariantMonoidal[WriterT[F, L, *]] { implicit def F0: ContravariantMonoidal[F] diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 586abc48c8..87027a23db 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -59,7 +59,7 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { implicit def catsStdInstancesForEither[A] : MonadError[Either[A, *], A] with Traverse[Either[A, *]] with Align[Either[A, *]] = new MonadError[Either[A, *], A] with Traverse[Either[A, *]] with Align[Either[A, *]] { - override def unit: Either[A, Unit] = Either.unit + override val unit: Either[A, Unit] = Either.unit def pure[B](b: B): Either[A, B] = Right(b) diff --git a/core/src/main/scala/cats/instances/eq.scala b/core/src/main/scala/cats/instances/eq.scala index 2bd862ec08..769bbb4247 100644 --- a/core/src/main/scala/cats/instances/eq.scala +++ b/core/src/main/scala/cats/instances/eq.scala @@ -23,14 +23,14 @@ package cats package instances trait EqInstances extends kernel.instances.EqInstances { - implicit val catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] = - new ContravariantMonoidal[Eq] { + implicit def catsDecidableForEq: Decidable[Eq] = + new Decidable[Eq] { /** * Defaults to the trivial equivalence relation * contracting the type to a point */ - def unit: Eq[Unit] = Eq.allEqual + val unit: Eq[Unit] = Eq.allEqual /** * Derive an `Eq` for `B` given an `Eq[A]` and a function `B => A`. @@ -42,5 +42,17 @@ trait EqInstances extends kernel.instances.EqInstances { def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] = (left, right) => fa.eqv(left._1, right._1) && fb.eqv(left._2, right._2) + + def sum[A, B](fa: Eq[A], fb: Eq[B]): Eq[Either[A, B]] = + Eq.instance { + case (Left(a1), Left(a2)) => fa.eqv(a1, a2) + case (Right(b1), Right(b2)) => fb.eqv(b1, b2) + case _ => false + } + + override val zero: Eq[Nothing] = Eq.allEqual[Nothing] } + + val catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] = + catsDecidableForEq } diff --git a/core/src/main/scala/cats/instances/equiv.scala b/core/src/main/scala/cats/instances/equiv.scala index 19916ab3f9..37de377bfd 100644 --- a/core/src/main/scala/cats/instances/equiv.scala +++ b/core/src/main/scala/cats/instances/equiv.scala @@ -23,8 +23,8 @@ package cats package instances trait EquivInstances { - implicit val catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] = - new ContravariantMonoidal[Equiv] { + implicit def catsDecidableForEquiv: Decidable[Equiv] = + new Decidable[Equiv] { /** * Defaults to trivially contracting the type @@ -51,5 +51,19 @@ trait EquivInstances { def equiv(l: (A, B), r: (A, B)): Boolean = fa.equiv(l._1, r._1) && fb.equiv(l._2, r._2) } + + def sum[A, B](fa: Equiv[A], fb: Equiv[B]): Equiv[Either[A, B]] = + new Equiv[Either[A, B]] { + def equiv(x: Either[A, B], y: Either[A, B]): Boolean = (x, y) match { + case (Left(a1), Left(a2)) => fa.equiv(a1, a2) + case (Right(b1), Right(b2)) => fb.equiv(b1, b2) + case _ => false + } + } + + override val zero: Equiv[Nothing] = Equiv.by[Nothing, Unit](_ => ()) } + + val catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] = + catsDecidableForEquiv } diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 29a07db641..91f945dbe5 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -22,7 +22,6 @@ package cats package instances -import cats.Contravariant import cats.arrow.{ArrowChoice, Category, CommutativeArrow} import cats.data.AndThen @@ -80,6 +79,19 @@ private[instances] trait FunctionInstancesBinCompat0 { Deferred(() => cachedFa) } } + + implicit def catsStdDecidableForPredicate: Decidable[* => Boolean] = + new Decidable[* => Boolean] { + val unit: Unit => Boolean = Function.const(false) + def contramap[A, B](fa: A => Boolean)(f: B => A): B => Boolean = + fa.compose(f) + def product[A, B](fa: A => Boolean, fb: B => Boolean): ((A, B)) => Boolean = { case (a, b) => + fa(a) || fb(b) + } + def sum[A, B](fa: A => Boolean, fb: B => Boolean): Either[A, B] => Boolean = + either => either.fold(fa, fb) + val zero: Nothing => Boolean = Function.const(true) + } } sealed private[instances] trait Function0Instances extends Function0Instances0 { @@ -203,7 +215,6 @@ sealed private[instances] trait Function1Instances extends Function1Instances0 { override def combineK[A](x: Endo[A], y: Endo[A]): Endo[A] = AndThen(category.compose(x, y)) } - } sealed private[instances] trait Function1Instances0 { diff --git a/core/src/main/scala/cats/instances/order.scala b/core/src/main/scala/cats/instances/order.scala index 66db216969..cae36f1474 100644 --- a/core/src/main/scala/cats/instances/order.scala +++ b/core/src/main/scala/cats/instances/order.scala @@ -25,7 +25,6 @@ package instances import cats.kernel.instances.unit._ trait OrderInstances extends kernel.instances.OrderInstances { - implicit val catsContravariantMonoidalForOrder: ContravariantMonoidal[Order] = new ContravariantMonoidal[Order] { diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 60d6d2b150..16643b4362 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -50,6 +50,7 @@ trait AllSyntax with ComonadSyntax with ComposeSyntax with ContravariantSyntax + with DecidableSyntax with DistributiveSyntax with ContravariantMonoidalSyntax with ContravariantSemigroupalSyntax diff --git a/core/src/main/scala/cats/syntax/decidable.scala b/core/src/main/scala/cats/syntax/decidable.scala new file mode 100644 index 0000000000..7e69564a63 --- /dev/null +++ b/core/src/main/scala/cats/syntax/decidable.scala @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package cats +package syntax + +import cats.Decidable + +trait DecidableSyntax extends ContravariantMonoidalSyntax { + implicit final def catsSyntaxDecidable[F[_], A]( + fa: F[A] + )(implicit F: Decidable[F]): Decidable.Ops[F, A] = + new Decidable.Ops[F, A] { + type TypeClassType = Decidable[F] + + val self = fa + val typeClassInstance = F + } +} diff --git a/docs/datatypes/oneand.md b/docs/datatypes/oneand.md index 9c4dbdc0a2..e34d812bba 100644 --- a/docs/datatypes/oneand.md +++ b/docs/datatypes/oneand.md @@ -19,4 +19,4 @@ to represent other "non-empty" data structures e.g. ```scala mdoc:silent type NonEmptyStream[A] = OneAnd[Stream, A] -``` \ No newline at end of file +``` diff --git a/docs/typeclasses/decidable.md b/docs/typeclasses/decidable.md new file mode 100644 index 0000000000..c24df9198a --- /dev/null +++ b/docs/typeclasses/decidable.md @@ -0,0 +1,57 @@ +# Decidable + +The `Decidable` type class is an extension of [`ContravariantMonoidal`](contravariantmonoidal.md) that allows the +definition of a `sum` function, allowing us to make choices in consuming our input. + +It is the contravariant analogue of the [`Alternative`](alternative.md) type class. + +```scala mdoc:silent +import cats.ContravariantMonoidal + +trait Decidable[F[_]] extends ContravariantMonoidal[F] { + def sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] + def zero[A]: F[Nothing] + + def decide[A, B, C](fa: F[A], fb: F[B])(f: C => Either[A, B]): F[C] = + contramap(sum(fa, fb))(f) + + def lose[A](f: A => Nothing): F[A] = contramap[Nothing, A](zero)(f) +} +``` + +`sum` lets us implement the `decide` function. Note how `decide` says, no matter what the outcome of a possibly multi-outcome procedure, I can consume either outcome. + +In other words, if we have two contexts `F[A]` and `F[B]` and we have a procedure `C => Either[A, B]` that could produce either an A or a B depending on the input, we have a way to produce a context `F[C]`. + +The classic example of a `Decidable` is that of predicates `A => Boolean`, leaning on the fact that both `&&` and `^` are valid monoids on `Boolean`. + +We can write the instance as: + +```scala mdoc:silent +implicit val decideableForPredicates = new Decidable[* => Boolean] { + def zero[A]: Nothing => Boolean = _ => true + def unit: Unit => Boolean = Function.const(false) + def contramap[A, B](fa: A => Boolean)(f: B => A): B => Boolean = + fa.compose(f) + def product[A, B](fa: A => Boolean, fb: B => Boolean): ((A, B)) => Boolean = + (ab: (A, B)) => + ab match { + case (a, b) => fa(a) || fb(b) + } + def sum[A, B](fa: A => Boolean, fb: B => Boolean): Either[A, B] => Boolean = + either => either.fold(fa, fb) +} +``` + +This can be used to combine predicates over both sum types and product types. + +```scala mdoc +def isEven(i: Int): Boolean = i % 2 == 0 +def isDivisibleByThree(l: Long): Boolean = l % 3 == 0 + +def isEvenRightOrDivisibleThreeLeft: Either[Int, Long] => Boolean = + decideableForPredicates.sum(isEven, isDivisibleByThree) + +def isEvenRightAndDivisibleThreeLeft: ((Int, Long)) => Boolean = + decideableForPredicates.product(isEven, isDivisibleByThree) +``` diff --git a/docs/typeclasses/directory.conf b/docs/typeclasses/directory.conf index 08270eb62b..56eb8dc5db 100644 --- a/docs/typeclasses/directory.conf +++ b/docs/typeclasses/directory.conf @@ -12,6 +12,7 @@ laika.navigationOrder = [ comonad.md contravariant.md contravariantmonoidal.md + decidable.md eq.md foldable.md functor.md @@ -28,4 +29,4 @@ laika.navigationOrder = [ semigroupk.md show.md traverse.md -] \ No newline at end of file +] diff --git a/laws/src/main/scala/cats/laws/DecidableLaws.scala b/laws/src/main/scala/cats/laws/DecidableLaws.scala new file mode 100644 index 0000000000..5cb0fed61b --- /dev/null +++ b/laws/src/main/scala/cats/laws/DecidableLaws.scala @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package cats +package laws + +trait DecidableLaws[F[_]] extends ContravariantMonoidalLaws[F] { + implicit override def F: Decidable[F] + + def loseConsistency[A](f: A => Nothing): IsEq[F[A]] = + F.lose(f) <-> F.contramap[Nothing, A](F.zero)(f) + + def decideConsistency[A, B, C](fa: F[A], fb: F[B], f: C => Either[A, B]): IsEq[F[C]] = + F.decide(fa, fb)(f) <-> F.contramap(F.sum(fa, fb))(f) + + def decideRightIdentity[A](fa: F[A]): IsEq[F[A]] = + F.decide[Nothing, A, A](F.zero, fa)(Right.apply) <-> fa + + def decideLeftIdentity[A](fa: F[A]): IsEq[F[A]] = + F.decide[A, Nothing, A](fa, F.zero)(Left.apply) <-> fa + + def decidableDecideLeftIdentity[A](fa: F[A]): IsEq[F[A]] = + F.decide(fa, F.trivial[A])(Left.apply[A, A]) <-> fa + + def decidableDecideRightIdentity[A](fa: F[A]): IsEq[F[A]] = + F.decide(F.trivial[A], fa)(Right.apply[A, A]) <-> fa + + def decidableSumAssociativity[A, B, C]( + fa: F[A], + fb: F[B], + fc: F[C] + ): IsEq[F[Either[Either[A, B], C]]] = + F.contramap[Either[A, Either[B, C]], Either[Either[A, B], C]](F.sum(fa, F.sum(fb, fc))) { + case Left(Left(x)) => Left(x) + case Left(Right(x)) => Right(Left(x)) + case Right(x) => Right(Right(x)) + } <-> F.sum(F.sum(fa, fb), fc) + + def decidableRightDistributivity[A, B, C](fa: F[A], fb: F[B], f: C => A, g: C => B): IsEq[F[Either[C, C]]] = + F.contramap(F.sum(fa, fb))((eit: Either[C, C]) => + eit.fold( + f.andThen(Left.apply), + g.andThen(Right.apply) + ) + ) <-> F.sum(F.contramap(fa)(f), F.contramap(fb)(g)) + + def decidableRightDistributivitySum[A, B, C](fa: F[A], fb: F[B], fc: F[C]): IsEq[F[(A, Either[B, C])]] = + F.product(fa, F.sum(fb, fc)) <-> + F.contramap(F.sum(F.product(fa, fb), F.product(fa, fc))) { + case (a, Left(b)) => Left((a, b)) + case (a, Right(c)) => Right((a, c)) + } +} + +object DecidableLaws { + def apply[F[_]](implicit ev: Decidable[F]): DecidableLaws[F] = + new DecidableLaws[F] { def F: Decidable[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/DecidableTests.scala b/laws/src/main/scala/cats/laws/discipline/DecidableTests.scala new file mode 100644 index 0000000000..09fc38ecfe --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/DecidableTests.scala @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2015 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Cogen, Gen} +import org.scalacheck.Prop._ + +trait DecidableTests[F[_]] extends ContravariantMonoidalTests[F] { + def laws: DecidableLaws[F] + + def decidable[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)]], + EqCC: Eq[F[Either[C, C]]], + EqFEitABC: Eq[F[Either[Either[A, B], C]]], + EqFTupAEitBC: Eq[F[(A, Either[B, C])]], + EqFNothing: Eq[F[Nothing]], + iso: SemigroupalTests.Isomorphisms[F] + ): RuleSet = + new RuleSet { + implicit val ArbNothing: Arbitrary[Nothing => Nothing] = Arbitrary(Gen.const((n: Nothing) => n)) + val name = "decideable" + val parents = Seq(contravariantMonoidal[A, B, C]) + val bases = Seq.empty + val props = Seq( + "lose consistency" -> + forAll(laws.loseConsistency[Nothing] _), + "decide consistency" -> + forAll(laws.decideConsistency[A, B, C] _), + "decide right identity (zero)" -> + forAll(laws.decideRightIdentity[A] _), + "decide left identity (zero)" -> + forAll(laws.decideLeftIdentity[A] _), + "decidable left identity" -> + forAll(laws.decidableDecideLeftIdentity[A] _), + "decidable right identity" -> + forAll(laws.decidableDecideRightIdentity[A] _), + "decidable sum associativity" -> + forAll(laws.decidableSumAssociativity[A, B, C] _), + "decidable right distributivity" -> + forAll(laws.decidableRightDistributivity[A, B, C] _), + "decidable right sum distributivity" -> + forAll(laws.decidableRightDistributivitySum[A, B, C] _) + ) + } +} + +object DecidableTests { + def apply[F[_]: Decidable]: DecidableTests[F] = + new DecidableTests[F] { def laws: DecidableLaws[F] = DecidableLaws[F] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala b/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala index 2a244f9a41..8de09b45ea 100644 --- a/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala +++ b/laws/src/main/scala/cats/laws/discipline/ExhaustiveCheck.scala @@ -40,6 +40,9 @@ object ExhaustiveCheck { val allValues: List[A] = values } + implicit val catsLawExhaustiveCheckForNothing: ExhaustiveCheck[Nothing] = + instance[Nothing](List.empty[Nothing]) + implicit val catsLawsExhaustiveCheckForBoolean: ExhaustiveCheck[Boolean] = instance(List(false, true)) diff --git a/scalafix/v1_0_0/input/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala b/scalafix/v1_0_0/input/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala index f4417bcf6c..0c898935dd 100644 --- a/scalafix/v1_0_0/input/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala +++ b/scalafix/v1_0_0/input/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala @@ -12,4 +12,4 @@ object RenameEitherTLiftTTests { val fa: Option[Int] = Some(42) val et1: EitherT[Option, Nothing, Int] = EitherT.liftT(fa) val et2: EitherT[Option, Nothing, Int] = liftT(fa) -} \ No newline at end of file +} diff --git a/scalafix/v1_0_0/output/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala b/scalafix/v1_0_0/output/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala index 1d165a8723..9dab9c3221 100644 --- a/scalafix/v1_0_0/output/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala +++ b/scalafix/v1_0_0/output/src/main/scala/fix/v1_0_0/RenameEitherTLiftT.scala @@ -9,4 +9,4 @@ object RenameEitherTLiftTTests { val fa: Option[Int] = Some(42) val et1: EitherT[Option, Nothing, Int] = EitherT.liftF(fa) val et2: EitherT[Option, Nothing, Int] = liftF(fa) -} \ No newline at end of file +} diff --git a/tests/shared/src/test/scala/cats/tests/ContravariantSuite.scala b/tests/shared/src/test/scala/cats/tests/ContravariantSuite.scala index cb50ae2960..83fae30af4 100644 --- a/tests/shared/src/test/scala/cats/tests/ContravariantSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/ContravariantSuite.scala @@ -21,14 +21,14 @@ package cats.tests -import cats.{Contravariant, ContravariantMonoidal, ContravariantSemigroupal} +import cats.Eq import cats.data.Const -import cats.kernel.{Eq, Monoid, Semigroup} import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests} -import cats.laws.discipline.{ContravariantMonoidalTests, ExhaustiveCheck, MiniInt} +import cats.kernel.{Monoid, Semigroup} import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.eq._ -import org.scalacheck.{Arbitrary, Cogen} +import cats.laws.discipline.{DecidableTests, MiniInt} +import cats.laws.discipline.ExhaustiveCheck.catsLawExhaustiveCheckForNothing +import cats.{Contravariant, ContravariantMonoidal, ContravariantSemigroupal} import org.scalacheck.Prop._ class ContravariantSuite extends CatsSuite { @@ -43,26 +43,13 @@ class ContravariantSuite extends CatsSuite { } } - case class Predicate[A](run: A => Boolean) - - implicit val contravariantMonoidalPredicate: ContravariantMonoidal[Predicate] = - new ContravariantMonoidal[Predicate] { - def unit: Predicate[Unit] = Predicate[Unit](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))) - } - - implicit def eqPredicate[A: ExhaustiveCheck]: Eq[Predicate[A]] = - Eq.by[Predicate[A], A => Boolean](_.run) - - implicit def arbPredicate[A: Cogen]: Arbitrary[Predicate[A]] = - Arbitrary(implicitly[Arbitrary[A => Boolean]].arbitrary.map(f => Predicate(f))) - - checkAll("ContravariantMonoidal[Predicate]", - ContravariantMonoidalTests[Predicate].contravariantMonoidal[Boolean, Boolean, Boolean] - ) + { + implicit val eqForPredicateNothing: Eq[Predicate[Nothing]] = Predicate.eqPredicate[Nothing] + checkAll( + "Decidable[Predicate]", + DecidableTests[Predicate].decidable[Boolean, Boolean, Boolean] + ) + } { implicit val predicateMonoid: Monoid[Predicate[MiniInt]] = ContravariantMonoidal.monoid[Predicate, MiniInt] diff --git a/tests/shared/src/test/scala/cats/tests/EqSuite.scala b/tests/shared/src/test/scala/cats/tests/EqSuite.scala index f832faf99d..da0ee653fd 100644 --- a/tests/shared/src/test/scala/cats/tests/EqSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/EqSuite.scala @@ -21,10 +21,10 @@ package cats.tests -import cats.{Contravariant, ContravariantMonoidal, ContravariantSemigroupal, Invariant, Semigroupal} +import cats.{Contravariant, ContravariantMonoidal, ContravariantSemigroupal, Decidable, Invariant, Semigroupal} import cats.kernel.Eq import cats.kernel.laws.discipline.SerializableTests -import cats.laws.discipline.{ContravariantMonoidalTests, MiniInt} +import cats.laws.discipline.{DecidableTests, MiniInt} import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ @@ -33,8 +33,13 @@ class EqSuite extends CatsSuite { Contravariant[Eq] Semigroupal[Eq] ContravariantSemigroupal[Eq] + ContravariantMonoidal[Eq] + Decidable[Eq] - checkAll("Eq", ContravariantMonoidalTests[Eq].contravariantMonoidal[MiniInt, Boolean, Boolean]) - checkAll("ContravariantMonoidal[Eq]", SerializableTests.serializable(ContravariantMonoidal[Eq])) + // Since equivalence on functions (Nothing, Nothing) => Boolean is + // equivalence on Nothing => Boolean it should be vacuously true + implicit val eqForEqNothing: Eq[Eq[Nothing]] = Eq.allEqual[Eq[Nothing]] + checkAll("Eq", DecidableTests[Eq].decidable[MiniInt, Boolean, Boolean]) + checkAll("Decidable[Eq]", SerializableTests.serializable(Decidable[Eq])) } diff --git a/tests/shared/src/test/scala/cats/tests/EquivSuite.scala b/tests/shared/src/test/scala/cats/tests/EquivSuite.scala index 9c0d75eb71..7b7a526993 100644 --- a/tests/shared/src/test/scala/cats/tests/EquivSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/EquivSuite.scala @@ -21,7 +21,7 @@ package cats.tests -import cats.{Contravariant, ContravariantMonoidal, ContravariantSemigroupal, Invariant, Semigroupal} +import cats.{Contravariant, ContravariantMonoidal, ContravariantSemigroupal, Decidable, Eq, Invariant, Semigroupal} import cats.laws.discipline.arbitrary._ import cats.laws.discipline._ import cats.laws.discipline.eq._ @@ -33,11 +33,17 @@ class EquivSuite extends CatsSuite { Semigroupal[Equiv] ContravariantSemigroupal[Equiv] ContravariantMonoidal[Equiv] + Decidable[Equiv] checkAll("Contravariant[Equiv]", ContravariantTests[Equiv].contravariant[MiniInt, Int, Boolean]) checkAll("Semigroupal[Equiv]", SemigroupalTests[Equiv].semigroupal[MiniInt, Boolean, Boolean]) checkAll("ContravariantMonoidal[Equiv]", ContravariantMonoidalTests[Equiv].contravariantMonoidal[MiniInt, Boolean, Boolean] ) - checkAll("ContravariantMonoidal[Equiv]", SerializableTests.serializable(ContravariantMonoidal[Equiv])) + + // Since equivalence on functions (Nothing, Nothing) => Boolean is + // equivalence on Nothing => Boolean it should be vacuously true + implicit val eqEquivNothing: Eq[Equiv[Nothing]] = Eq.allEqual[Equiv[Nothing]] + checkAll("Decidable[Equiv]", DecidableTests[Equiv].decidable[MiniInt, Boolean, Boolean]) + checkAll("Decidable[Equiv]", SerializableTests.serializable(Decidable[Equiv])) } diff --git a/tests/shared/src/test/scala/cats/tests/FunctionSuite.scala b/tests/shared/src/test/scala/cats/tests/FunctionSuite.scala index 4be40b0921..b1e0501f2e 100644 --- a/tests/shared/src/test/scala/cats/tests/FunctionSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/FunctionSuite.scala @@ -26,6 +26,7 @@ import cats.{ Bimonad, Contravariant, ContravariantMonoidal, + Decidable, Defer, Distributive, Endo, @@ -35,6 +36,7 @@ import cats.{ Semigroupal } import cats.arrow.{ArrowChoice, Choice, CommutativeArrow} +import cats.instances.function._ import cats.kernel._ import cats.kernel.laws.discipline.{ BandTests, @@ -176,6 +178,14 @@ class FunctionSuite extends CatsSuite { ContravariantMonoidalTests[Function1[*, Long]].contravariantMonoidal[MiniInt, MiniInt, MiniInt] ) + // Isos for Decidable + implicit val isoCodomainBoolean: Isomorphisms[Function1[*, Boolean]] = + SemigroupalTests.Isomorphisms.invariant[Function1[*, Boolean]] + // Vacuously true that all elements of the empty set are equivalent + implicit val eqNothingPreciates: Eq[Nothing => Boolean] = Eq.allEqual[Nothing => Boolean] + checkAll("Function1[?, Boolean]", DecidableTests[Function1[*, Boolean]].decidable[MiniInt, MiniInt, MiniInt]) + checkAll("Decidable[? => Boolean]", SerializableTests.serializable(Decidable[Function1[*, Boolean]])) + // serialization tests for the various Function1-related instances checkAll("Semigroup[String => Semi]", SerializableTests.serializable(Semigroup[String => Semi])) checkAll("CommutativeSemigroup[String => Semi]", diff --git a/tests/shared/src/test/scala/cats/tests/IdTSuite.scala b/tests/shared/src/test/scala/cats/tests/IdTSuite.scala index 64334919bf..625060dc46 100644 --- a/tests/shared/src/test/scala/cats/tests/IdTSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/IdTSuite.scala @@ -88,6 +88,13 @@ class IdTSuite extends CatsSuite { ) } + { + // Predicates on Nothing are vacuously equivalent, because all vacuously true, and IdT of these are the same + implicit val eqPredicateNothing: Eq[IdT[Predicate, Nothing]] = Eq.allEqual[IdT[Predicate, Nothing]] + checkAll("IdT[* => Boolean], ?]", DecidableTests[IdT[Predicate, *]].decidable[MiniInt, MiniInt, MiniInt]) + checkAll("Decidable[IdT[* => Boolean, ?], ?]]", SerializableTests.serializable(Decidable[IdT[Predicate, *]])) + } + { implicit val F: FlatMap[ListWrapper] = ListWrapper.flatMap diff --git a/tests/shared/src/test/scala/cats/tests/KleisliSuite.scala b/tests/shared/src/test/scala/cats/tests/KleisliSuite.scala index 4a77d5f45a..d26df79c69 100644 --- a/tests/shared/src/test/scala/cats/tests/KleisliSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/KleisliSuite.scala @@ -25,9 +25,9 @@ import cats._ import cats.arrow._ import cats.data.{Const, EitherT, Kleisli, Reader, ReaderT} import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests} -import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ +import cats.laws.discipline._ import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.laws.discipline.{DeferTests, MonoidKTests, SemigroupKTests} import cats.syntax.all._ @@ -133,12 +133,30 @@ class KleisliSuite extends CatsSuite { checkAll("Kleisli[Option, MiniInt, *]", AlternativeTests[Kleisli[Option, MiniInt, *]].alternative[Int, Int, Int]) checkAll("Alternative[Kleisli[Option, Int, *]]", SerializableTests.serializable(Alternative[Kleisli[Option, Int, *]])) - checkAll("Kleisli[Const[String, *], MiniInt, *]", - ContravariantMonoidalTests[Kleisli[Const[String, *], MiniInt, *]].contravariantMonoidal[Int, Int, Int] - ) - checkAll("ContravariantMonoidal[Kleisli[Option, Int, *]]", - SerializableTests.serializable(ContravariantMonoidal[Kleisli[Const[String, *], Int, *]]) - ) + { + // Kleisli[Predicate, MiniInt, Nothing]] == MiniInt => Predicate[Nothing] == MiniInt => (Nothing => Boolean) + // but all predicates on nothing are equivalent, so all such mappings are equivalent + implicit val eqKleisliPreciateNothing: Eq[Kleisli[Predicate, MiniInt, Nothing]] = + Eq.allEqual[Kleisli[Predicate, MiniInt, Nothing]] + checkAll("Kleisli[Predicate, MiniInt, MiniInt]", + DecidableTests[Kleisli[Predicate, MiniInt, *]].decidable[MiniInt, MiniInt, MiniInt] + ) + checkAll( + "Decidable[Kleisli[Option, Int, *]]", + SerializableTests.serializable[Decidable[Kleisli[Predicate, MiniInt, *]]]( + Decidable[Kleisli[Predicate, MiniInt, *]] + ) + ) + } + + { + checkAll("Kleisli[Const[String, *], MiniInt, *]", + ContravariantMonoidalTests[Kleisli[Const[String, *], MiniInt, *]].contravariantMonoidal[Int, Int, Int] + ) + checkAll("ContravariantMonoidal[Kleisli[Option, Int, *]]", + SerializableTests.serializable(ContravariantMonoidal[Kleisli[Const[String, *], Int, *]]) + ) + } checkAll("Kleisli[Option, MiniInt, Int]", ApplicativeTests[Kleisli[Option, MiniInt, *]].applicative[Int, Int, Int]) checkAll("Applicative[Kleisli[Option, Int, *]]", SerializableTests.serializable(Applicative[Kleisli[Option, Int, *]])) diff --git a/tests/shared/src/test/scala/cats/tests/NestedSuite.scala b/tests/shared/src/test/scala/cats/tests/NestedSuite.scala index 1325d874c2..45a88e9549 100644 --- a/tests/shared/src/test/scala/cats/tests/NestedSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/NestedSuite.scala @@ -127,11 +127,26 @@ class NestedSuite extends CatsSuite { { // Applicative + ContravariantMonoidal functor composition - checkAll("Nested[Option, Const[String, *], *]", - ContravariantMonoidalTests[Nested[Option, Const[String, *], *]].contravariantMonoidal[Int, Int, Int] +// implicit val isos = Isomorphisms.invariant[Nested[Option, Const[String, *], *]] + 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, *], α]]]) ) - checkAll("ContravariantMonoidal[Nested[Option, Const[String, *], *]", - SerializableTests.serializable(ContravariantMonoidal[Nested[Option, Const[String, *], *]]) + } + + { + // Applicative + Decidable functor composition + // Only two values here, one for Some and one for None + implicit val eqForOptionPredicateNothing: Eq[Nested[Option, Predicate, Nothing]] = Eq.by(_.value.isDefined) + checkAll("Nested[Option, Predicate, ?]", + DecidableTests[Nested[Option, Predicate, *]].decidable[MiniInt, MiniInt, MiniInt] + ) + checkAll("Decidable[Nested[Option, Predicate, ?]]", + SerializableTests.serializable(Decidable[Nested[Option, Predicate, *]]) ) } diff --git a/tests/shared/src/test/scala/cats/tests/OpSuite.scala b/tests/shared/src/test/scala/cats/tests/OpSuite.scala index aa273e7b7b..5482a68321 100644 --- a/tests/shared/src/test/scala/cats/tests/OpSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/OpSuite.scala @@ -21,6 +21,7 @@ package cats.tests +import cats.{Contravariant, ContravariantMonoidal, Decidable} import cats.arrow._ import cats.data.{Kleisli, Op} import cats.kernel.Eq @@ -42,6 +43,33 @@ class OpSuite extends CatsSuite { checkAll("Category[Op[Function1, *, *]]", SerializableTests.serializable(Category[Op[Function1, *, *]])) } + { + // Op[Function1, Monoid, Nothing] = Nothing => (A: Monoid) which is vacously equivalent + implicit val eqForOpFunction1Nothing: Eq[Op[Function1, Int, Nothing]] = Eq.allEqual + checkAll("Op[Function1, Monoid, ?]", DecidableTests[Op[Function1, Int, *]].decidable[MiniInt, MiniInt, MiniInt]) + checkAll("Decidable[Op[Function1, Monoid, ?]]", SerializableTests.serializable(Decidable[Op[Function1, Int, *]])) + } + + { + checkAll("Op[Function1, Monoid, ?]", + ContravariantMonoidalTests[Op[Function1, Int, *]].contravariantMonoidal[MiniInt, MiniInt, MiniInt] + ) + checkAll("ContravariantMonoidal[Op[Function1, Monoid, ?]]", + SerializableTests.serializable(ContravariantMonoidal[Op[Function1, Int, *]]) + ) + + } + + { + checkAll("Op[Function1, Unit, ?]", + ContravariantTests[Op[Function1, Unit, *]].contravariant[MiniInt, MiniInt, MiniInt] + ) + checkAll("Contravariant[Op[Function1, Unit, ?]]", + SerializableTests.serializable(Contravariant[Op[Function1, Int, *]]) + ) + + } + /** * Testing that implicit resolution works. If it compiles, the "test" passes. */ diff --git a/tests/shared/src/test/scala/cats/tests/Predicate.scala b/tests/shared/src/test/scala/cats/tests/Predicate.scala new file mode 100644 index 0000000000..78e5717160 --- /dev/null +++ b/tests/shared/src/test/scala/cats/tests/Predicate.scala @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package cats.tests + +import cats.Decidable +import cats.instances.function.catsStdDecidableForPredicate +import cats.kernel.Eq +import cats.laws.discipline.eq._ +import cats.laws.discipline.ExhaustiveCheck +import org.scalacheck.{Arbitrary, Cogen} + +case class Predicate[A](run: A => Boolean) +object Predicate { + implicit val decidablePredicate: Decidable[Predicate] = + new Decidable[Predicate] { + def unit: Predicate[Unit] = Predicate[Unit](catsStdDecidableForPredicate.unit) + def product[A, B](fa: Predicate[A], fb: Predicate[B]): Predicate[(A, B)] = + Predicate(catsStdDecidableForPredicate.product(fa.run, fb.run)) + def contramap[A, B](fa: Predicate[A])(f: B => A): Predicate[B] = + Predicate(catsStdDecidableForPredicate.contramap(fa.run)(f)) + def sum[A, B](fa: Predicate[A], fb: Predicate[B]): Predicate[Either[A, B]] = + Predicate(catsStdDecidableForPredicate.sum(fa.run, fb.run)) + def zero: Predicate[Nothing] = Predicate[Nothing](catsStdDecidableForPredicate.zero) + } + + implicit def eqPredicate[A: ExhaustiveCheck]: Eq[Predicate[A]] = + Eq.by[Predicate[A], A => Boolean](_.run) + + implicit def arbPredicate[A: Cogen]: Arbitrary[Predicate[A]] = + Arbitrary(implicitly[Arbitrary[A => Boolean]].arbitrary.map(f => Predicate(f))) +} diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala index 829577afd5..bc4985fe0d 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala @@ -115,12 +115,20 @@ object SyntaxSuite { val fb: F[B] = fa.imap(f)(g) } - def testInvariantFunctor[F[_]: Contravariant, A, B]: Unit = { + def testContravariantFunctor[F[_]: Contravariant, A, B]: Unit = { val fa = mock[F[A]] val f = mock[B => A] val fb: F[B] = fa.contramap(f) } + def testDecidableFunctor[F[_]: Decidable, A, B, C]: Unit = { + val fa = mock[F[A]] + val fb = mock[F[B]] + val f = mock[C => Either[A, B]] + val fc = fa.sum(fb) + val fd = fa.decide(fb)(f) + } + def testFoldable[F[_]: Foldable, G[_]: Applicative: MonoidK, A: Monoid, B, Z]: Unit = { val fa = mock[F[A]] val b = mock[B] diff --git a/tests/shared/src/test/scala/cats/tests/Tuple2KSuite.scala b/tests/shared/src/test/scala/cats/tests/Tuple2KSuite.scala index ac4f93a032..064f7681a0 100644 --- a/tests/shared/src/test/scala/cats/tests/Tuple2KSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/Tuple2KSuite.scala @@ -52,6 +52,8 @@ class Tuple2KSuite extends CatsSuite { SerializableTests.serializable(Contravariant[λ[α => Tuple2K[Show, Order, α]]]) ) + implicit val isoConst: Isomorphisms[λ[α => Tuple2K[Const[String, *], Const[String, *], α]]] = + Isomorphisms.invariant[λ[α => Tuple2K[Const[String, *], Const[String, *], α]]] checkAll( "Tuple2K[Const[String, *], Const[Int, *], Int]", ContravariantMonoidalTests[λ[α => Tuple2K[Const[String, *], Const[Int, *], α]]].contravariantMonoidal[Int, Int, Int] @@ -61,6 +63,18 @@ class Tuple2KSuite extends CatsSuite { SerializableTests.serializable(ContravariantMonoidal[λ[α => Tuple2K[Const[String, *], Const[Int, *], α]]]) ) + { + // Predicates on Nothing are vacuous, so their product are also + implicit val eqForTuple2KPredicateNothing: Eq[Tuple2K[Predicate, Predicate, Nothing]] = Eq.allEqual + checkAll("Tuple2K[Predicate, Predicate, MiniInt]", + DecidableTests[Tuple2K[Predicate, Predicate, *]].decidable[MiniInt, MiniInt, MiniInt] + ) + checkAll( + "Decidable[Tuple2K[Predicate, Predicate, MiniInt]]", + SerializableTests.serializable(Decidable[Tuple2K[Predicate, Predicate, *]]) + ) + } + checkAll("Show[Tuple2K[Option, Option, Int]]", SerializableTests.serializable(Show[Tuple2K[Option, Option, Int]])) {