diff --git a/core/src/main/scala/cats/TFunctor.scala b/core/src/main/scala/cats/TFunctor.scala new file mode 100644 index 0000000000..dda4c28f2e --- /dev/null +++ b/core/src/main/scala/cats/TFunctor.scala @@ -0,0 +1,42 @@ +package cats + +import simulacrum.typeclass + +/** + * This is an endofunctor in the category of endofunctors in `Skal`. + * + * `Skal` is the category of scala types. Functors in `Skal` + * is encoded as `Functor`. The functors in `Skal` themselves forms + * a category, let's denote it as `F[Skal]`. + * In `F[Skal]`, functors of Skal, e.g. `Option[_]` and `Either[E, _]`, are objects, + * while natural transformations, e.g. `Option ~> Either[E, ?]`, are arrows. + * A endofunctor in `F[Skal]` maps one set of functors of `Skal` to another + * set of functors of `Skal` while preserving the structures. + * + * For `TFunctor`, the domain is `F[_]`, the codomain is `T[F, _]`, both are + * functors in `Skal`. The `TFunctor` provides a mapping from the arrows between + * `F[_]` and `G[_]`, i.e. `F ~> G` to arrows between `T[F, _]` and `T[G, _]`, + * i.e. `T[F, ?] ~> T[G, ?]`. The `lift` method makes this intention clear. + * + * In `cats.core`, examples of such `TFunctor`s are monad transformers such + * as `OptionT`, `EitherT` + * + */ +@typeclass trait TFunctor[T[_[_], _]] { + def mapNT[F[_], G[_], A](h: T[F, A])(f: F ~> G): T[G, A] + + /** + * Lift a `F ~> G` to a `T[F, ?] ~> T[G, ?]`. + * + * {{{ + * scala> import cats.implicits._, cats.data.OptionT + * scala> val lv: List ~> Vector = λ[List ~> Vector](_.toVector) + * scala> val olv: OptionT[List, ?] ~> OptionT[Vector, ?] = TFunctor[OptionT].liftNT(lv) + * scala> olv(OptionT.liftF(List(1))) + * res0: OptionT[Vector, Int] = OptionT(Vector(Some(1))) + * }}} + */ + def liftNT[F[_], G[_]](f: F ~> G): T[F, ?] ~> T[G, ?] = + λ[T[F, ?] ~> T[G, ?]](hf => mapNT(hf)(f)) +} + diff --git a/core/src/main/scala/cats/data/EitherK.scala b/core/src/main/scala/cats/data/EitherK.scala index 37f6d13854..4c0b1dedaf 100644 --- a/core/src/main/scala/cats/data/EitherK.scala +++ b/core/src/main/scala/cats/data/EitherK.scala @@ -152,6 +152,11 @@ private[data] sealed abstract class EitherKInstances0 extends EitherKInstances1 private[data] sealed abstract class EitherKInstances extends EitherKInstances0 { + implicit def catsDataTFunctorForEitherK[L[_]]: TFunctor[EitherK[L, ?[_], ?]] = new TFunctor[EitherK[L, ?[_], ?]] { + def mapNT[F[_], G[_], A](ek: EitherK[L, F, A])(f: F ~> G): EitherK[L, G, A] = EitherK(ek.run.map(f.apply)) + } + + implicit def catsDataComonadForEitherK[F[_], G[_]](implicit F0: Comonad[F], G0: Comonad[G]): Comonad[EitherK[F, G, ?]] = new EitherKComonad[F, G] { implicit def F: Comonad[F] = F0 diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 12ccca61db..4dd203695b 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -420,6 +420,11 @@ object EitherT extends EitherTInstances { private[data] abstract class EitherTInstances extends EitherTInstances1 { + implicit def catsDataTFunctorForEitherT[A]: TFunctor[EitherT[?[_], A, ?]] = new TFunctor[EitherT[?[_], A, ?]] { + def mapNT[F[_], G[_], B](h: EitherT[F, A, B])(f: F ~> G): EitherT[G, A, B] = EitherT(f(h.value)) + } + + implicit def catsDataOrderForEitherT[F[_], L, R](implicit F: Order[F[Either[L, R]]]): Order[EitherT[F, L, R]] = new EitherTOrder[F, L, R] { val F0: Order[F[Either[L, R]]] = F diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala index 350e50329a..c52f43245a 100644 --- a/core/src/main/scala/cats/data/Func.scala +++ b/core/src/main/scala/cats/data/Func.scala @@ -31,6 +31,11 @@ object Func extends FuncInstances { } private[data] abstract class FuncInstances extends FuncInstances0 { + + implicit def catsDataTFunctorForFunc[A]: TFunctor[Func[?[_], A, ?]] = new TFunctor[Func[?[_], A, ?]] { + def mapNT[F[_], G[_], B](h: Func[F, A, B])(f: F ~> G): Func[G, A, B] = Func.func(h.run.andThen(f.apply)) + } + implicit def catsDataApplicativeForFunc[F[_], C](implicit FF: Applicative[F]): Applicative[λ[α => Func[F, C, α]]] = new FuncApplicative[F, C] { def F: Applicative[F] = FF diff --git a/core/src/main/scala/cats/data/IdT.scala b/core/src/main/scala/cats/data/IdT.scala index c1d013a6a4..d5349476a7 100644 --- a/core/src/main/scala/cats/data/IdT.scala +++ b/core/src/main/scala/cats/data/IdT.scala @@ -152,6 +152,10 @@ private[data] sealed abstract class IdTInstances0 extends IdTInstances1 { private[data] sealed abstract class IdTInstances extends IdTInstances0 { + implicit val catsDataTFunctorForIdT: TFunctor[IdT] = new TFunctor[IdT] { + def mapNT[F[_], G[_], A](h: IdT[F, A])(f: F ~> G): IdT[G, A] = IdT(f(h.value)) + } + implicit def catsDataNonEmptyTraverseForIdT[F[_]](implicit F: NonEmptyTraverse[F]): NonEmptyTraverse[IdT[F, ?]] = new IdTNonEmptyTraverse[F] { implicit val F0: NonEmptyTraverse[F] = F } diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 501c3101c4..63ebb46893 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -89,6 +89,11 @@ private[data] sealed trait KleisliFunctions { } private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { + + implicit def catsDataTFunctorForKleisli[A]: TFunctor[Kleisli[?[_], A, ?]] = new TFunctor[Kleisli[?[_], A, ?]] { + def mapNT[F[_], G[_], B](k: Kleisli[F, A, B])(f: F ~> G): Kleisli[G, A, B] = k.transform(f) + } + implicit def catsDataCommutativeMonadForKleisli[F[_], A, B](implicit F0: CommutativeMonad[F]): CommutativeMonad[Kleisli[F, A, ?]] = new KleisliMonad[F, A] with CommutativeMonad[Kleisli[F, A, ?]] { implicit def F: Monad[F] = F0 diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index 9540772f29..1ec02b5bed 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -28,6 +28,12 @@ final case class Nested[F[_], G[_], A](value: F[G[A]]) object Nested extends NestedInstances private[data] sealed abstract class NestedInstances extends NestedInstances0 { + + + implicit def catsDataTFunctorForNested[I[_]]: TFunctor[Nested[?[_], I, ?]] = new TFunctor[Nested[?[_], I, ?]] { + def mapNT[F[_], G[_], A](n: Nested[F, I, A])(f: F ~> G): Nested[G, I, A] = Nested(f(n.value)) + } + implicit def catsDataEqForNested[F[_], G[_], A](implicit FGA: Eq[F[G[A]]]): Eq[Nested[F, G, A]] = FGA.on(_.value) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 43963d461a..4e3af2e4aa 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -100,6 +100,10 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { private[data] sealed trait OneAndInstances extends OneAndLowPriority3 { + implicit val catsDataTFunctorForOneAnd: TFunctor[OneAnd] = new TFunctor[OneAnd] { + def mapNT[F[_], G[_], A](o: OneAnd[F, A])(f: F ~> G): OneAnd[G, A] = OneAnd(o.head, f(o.tail)) + } + implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = new Eq[OneAnd[F, A]]{ def eqv(x: OneAnd[F, A], y: OneAnd[F, A]): Boolean = x === y diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index dfe8665675..db1f9d2379 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -196,6 +196,11 @@ object OptionT extends OptionTInstances { } private[data] sealed trait OptionTInstances extends OptionTInstances0 { + + implicit val catsDataTFunctorForOptionT: TFunctor[OptionT] = new TFunctor[OptionT] { + def mapNT[F[_], G[_], A](h: OptionT[F, A])(f: F ~> G): OptionT[G, A] = OptionT(f(h.value)) + } + implicit def catsDataMonadForOptionT[F[_]](implicit F0: Monad[F]): Monad[OptionT[F, ?]] = new OptionTMonad[F] { implicit val F = F0 } diff --git a/core/src/main/scala/cats/data/Tuple2K.scala b/core/src/main/scala/cats/data/Tuple2K.scala index dfa895a9f1..65dfcb3e88 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -13,6 +13,11 @@ final case class Tuple2K[F[_], G[_], A](first: F[A], second: G[A]) object Tuple2K extends Tuple2KInstances private[data] sealed abstract class Tuple2KInstances extends Tuple2KInstances0 { + + implicit def catsDataTFunctorForTuple2K[L[_]]: TFunctor[Tuple2K[L, ?[_], ?]] = new TFunctor[Tuple2K[L, ?[_], ?]] { + def mapNT[F[_], G[_], A](ek: Tuple2K[L, F, A])(f: F ~> G): Tuple2K[L, G, A] = Tuple2K(ek.first, f(ek.second)) + } + implicit def catsDataOrderForTuple2K[F[_], G[_], A](implicit FF: Order[F[A]], GF: Order[G[A]]): Order[Tuple2K[F, G, A]] = new Tuple2KOrder[F, G, A] { def F: Order[F[A]] = FF def G: Order[G[A]] = GF diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 204d99e7b5..8ef045d6f5 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -67,6 +67,11 @@ object WriterT extends WriterTInstances with WriterTFunctions { } private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { + + implicit def catsDataTFunctorForWriterT[A]: TFunctor[WriterT[?[_], A, ?]] = new TFunctor[WriterT[?[_], A, ?]] { + def mapNT[F[_], G[_], B](w: WriterT[F, A, B])(f: F ~> G): WriterT[G, A, B] = WriterT(f(w.run)) + } + implicit def catsDataCommutativeMonadForWriterT[F[_], L](implicit F: CommutativeMonad[F], L: CommutativeMonoid[L]): CommutativeMonad[WriterT[F, L, ?]] = new WriterTMonad[F, L] with CommutativeMonad[WriterT[F, L, ?]] { implicit val F0: Monad[F] = F diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 2f6d9be450..fd162488d1 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -37,6 +37,7 @@ trait AllSyntax with SemigroupKSyntax with ShowSyntax with StrongSyntax + with TFunctorSyntax with TraverseSyntax with NonEmptyTraverseSyntax with ValidatedSyntax diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index f8c035de2a..671870341e 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -37,6 +37,7 @@ package object syntax { object semigroupk extends SemigroupKSyntax object show extends ShowSyntax object strong extends StrongSyntax + object tfunctor extends TFunctorSyntax object traverse extends TraverseSyntax object nonEmptyTraverse extends NonEmptyTraverseSyntax object validated extends ValidatedSyntax diff --git a/core/src/main/scala/cats/syntax/tfunctor.scala b/core/src/main/scala/cats/syntax/tfunctor.scala new file mode 100644 index 0000000000..49c16c4b6b --- /dev/null +++ b/core/src/main/scala/cats/syntax/tfunctor.scala @@ -0,0 +1,4 @@ +package cats +package syntax + +trait TFunctorSyntax extends TFunctor.ToTFunctorOps diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 8d029fae51..a676e58b6e 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -127,6 +127,17 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { case FlatMapped(c, g) => M.map(c.foldMap(f))(cc => Left(g(cc))) }) + /** + * Changes the underlying `Monad` for this `Free`, ie. + * turning this `Free[S, A]` into a `Free[N, A]`. + */ + def hoist[N[_]](mn: FunctionK[S, N]): Free[N, A] = + step match { + case Pure(a) => Pure(a) + case Suspend(m) => Suspend(mn(m)) + case FlatMapped(c, g) => FlatMapped(c.hoist(mn), g.andThen(_.hoist(mn))) + } + /** * Compile your free monad into another language by changing the * suspension functor using the given natural transformation `f`. @@ -249,4 +260,8 @@ object Free { override def map[A, B](fa: Free[S, A])(f: A => B): Free[S, B] = fa.map(f) def flatMap[A, B](a: Free[S, A])(f: A => Free[S, B]): Free[S, B] = a.flatMap(f) } + + implicit val catsFreeTFunctorForFree: TFunctor[Free] = new TFunctor[Free] { + def mapNT[F[_], G[_], A](h: Free[F, A])(f: F ~> G): Free[G, A] = h.hoist(f) + } } diff --git a/free/src/main/scala/cats/free/FreeT.scala b/free/src/main/scala/cats/free/FreeT.scala index cb70e22b0f..30b6983e6b 100644 --- a/free/src/main/scala/cats/free/FreeT.scala +++ b/free/src/main/scala/cats/free/FreeT.scala @@ -2,7 +2,6 @@ package cats package free import scala.annotation.tailrec - import cats.arrow.FunctionK /** @@ -183,6 +182,11 @@ object FreeT extends FreeTInstances { } private[free] sealed trait FreeTInstances extends FreeTInstances0 { + + implicit def catsFreeTFunctorForFreeT[S[_]]: TFunctor[FreeT[S, ?[_], ?]] = new TFunctor[FreeT[S, ?[_], ?]] { + def mapNT[F[_], G[_], A](ft: FreeT[S, F, A])(f: F ~> G): FreeT[S, G, A] = ft.hoist(f) + } + implicit def catsFreeMonadErrorForFreeT[S[_], M[_], E](implicit E: MonadError[M, E]): MonadError[FreeT[S, M, ?], E] = new MonadError[FreeT[S, M, ?], E] with FreeTMonad[S, M] { override def M = E diff --git a/free/src/test/scala/cats/free/FreeTTests.scala b/free/src/test/scala/cats/free/FreeTTests.scala index a5f2f30f1e..c693111dca 100644 --- a/free/src/test/scala/cats/free/FreeTTests.scala +++ b/free/src/test/scala/cats/free/FreeTTests.scala @@ -13,6 +13,15 @@ import org.scalacheck.{Arbitrary, Gen, Cogen} class FreeTTests extends CatsSuite { import FreeTTests._ + { + implicit val freeOptionListEq: Eq[FreeT[Option, List, Int]] = new Eq[FreeT[Option, List, Int]]{ + val ol = λ[Option ~> List](_.toList) + def eqv(a: FreeT[Option, List, Int], b: FreeT[Option, List, Int]) = a.runM(o => ol(o)) === b.runM(o => ol(o)) + } + + checkAll("FreeT[Option, ?[_], ?]", TFunctorTests[FreeT[Option, ?[_], ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[FreeT[Option, ?[_], ?]]", SerializableTests.serializable(TFunctor[FreeT[Option, ?[_], ?]])) + } { implicit val freeTFlatMap: FlatMap[FreeTOption] = FreeT.catsFreeFlatMapForFreeT[Option, Option] @@ -199,7 +208,7 @@ trait FreeTTestsInstances { implicit def intStateArb[A: Arbitrary]: Arbitrary[IntState[A]] = catsLawArbitraryForState[Int, A] - implicit def freeTOptionEq[A](implicit A: Eq[A], OM: Monad[Option]): Eq[FreeTOption[A]] = new Eq[FreeTOption[A]] { + implicit def freeTOptionEq[A](implicit A: Eq[A]): Eq[FreeTOption[A]] = new Eq[FreeTOption[A]] { def eqv(a: FreeTOption[A], b: FreeTOption[A]) = Eq[Option[A]].eqv(a.runM(identity), b.runM(identity)) } @@ -207,3 +216,4 @@ trait FreeTTestsInstances { def eqv(a: FreeTState[A], b: FreeTState[A]) = Eq[IntState[A]].eqv(a.runM(identity)(SM, SM), b.runM(identity)(SM, SM)) } } + diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 4097b36e10..6a0f93ee02 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -3,11 +3,10 @@ package free import cats.arrow.FunctionK import cats.data.EitherK -import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests} -import cats.laws.discipline.arbitrary.catsLawsArbitraryForFn0 +import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests, TFunctorTests} +import cats.laws.discipline.arbitrary._ import cats.tests.CatsSuite - -import org.scalacheck.{Arbitrary, Gen, Cogen} +import org.scalacheck.{Arbitrary, Cogen, Gen} import Arbitrary.arbFunction1 class FreeTests extends CatsSuite { @@ -15,6 +14,9 @@ class FreeTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[Free[Option, ?]] + checkAll("Free", TFunctorTests[Free].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[Free]", SerializableTests.serializable(TFunctor[Free])) + checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) diff --git a/laws/src/main/scala/cats/laws/TFunctorLaws.scala b/laws/src/main/scala/cats/laws/TFunctorLaws.scala new file mode 100644 index 0000000000..1d3cec6510 --- /dev/null +++ b/laws/src/main/scala/cats/laws/TFunctorLaws.scala @@ -0,0 +1,24 @@ +package cats +package laws + + +import cats.arrow.FunctionK +import syntax.all._ +import cats.~> + +trait TFunctorLaws[T[_[_], _]]{ + implicit def T: TFunctor[T] + + def covariantIdentity[F[_], A](fg: T[F, A]): IsEq[T[F, A]] = + fg.mapNT(FunctionK.id[F]) <-> fg + + def covariantComposition[F[_], G[_], H[_], A](fa: T[F, A], f: F ~> G, g: G ~> H): IsEq[T[H, A]] = + fa.mapNT(f).mapNT(g) <-> fa.mapNT(f andThen g) + +} + +object TFunctorLaws { + def apply[T[_[_], _]](implicit ev: TFunctor[T]): TFunctorLaws[T] = + new TFunctorLaws[T] { def T: TFunctor[T] = ev } +} + diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 963d71faa2..012f301f3b 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -164,6 +164,17 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForReaderWriterStateT[F[_]: Applicative, E, L, S, A](implicit F: Arbitrary[(E, S) => F[(L, S, A)]]): Arbitrary[ReaderWriterStateT[F, E, L, S, A]] = Arbitrary(F.arbitrary.map(ReaderWriterStateT(_))) + implicit val catsLawArbitraryForFunctionKOptionList: Arbitrary[Option ~> List] = Arbitrary(Gen.const(λ[Option ~> List](_.toList))) + + implicit val catsLawArbitraryForFunctionKListOption: Arbitrary[List ~> Option] = Arbitrary(Gen.const(λ[List ~> Option](_.headOption))) + + implicit val catsLawArbitraryForFunctionKListVector: Arbitrary[List ~> Vector] = Arbitrary(Gen.const(λ[List ~> Vector](_.toVector))) + + implicit val catsLawArbitraryForFunctionKVectorList: Arbitrary[Vector ~> List] = Arbitrary(Gen.const(λ[Vector ~> List](_.toList))) + + implicit val catsLawArbitraryForFunctionKVectorOption: Arbitrary[Vector ~> Option] = Arbitrary(Gen.const(λ[Vector ~> Option](_.headOption))) + + implicit val catsLawArbitraryForFunctionKOptionVector: Arbitrary[Option ~> Vector] = Arbitrary(Gen.const(λ[Option ~> Vector](_.toVector))) } private[discipline] sealed trait ArbitraryInstances0 { diff --git a/laws/src/main/scala/cats/laws/discipline/TFunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/TFunctorTests.scala new file mode 100644 index 0000000000..b7ba494144 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/TFunctorTests.scala @@ -0,0 +1,37 @@ +package cats +package laws +package discipline + + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ +import cats.{Eq, ~>} +import org.typelevel.discipline.Laws + +trait TFunctorTests[T[_[_], _]] extends Laws { + def laws: TFunctorLaws[T] + + def tfunctor[F[_], G[_], H[_], A: Arbitrary](implicit + ArbFA: Arbitrary[T[F, A]], + ArbitraryG: Arbitrary[F[A]], + ArbitraryH: Arbitrary[G[A]], + ArbitraryI: Arbitrary[H[A]], + ArbitraryFK: Arbitrary[F ~> G], + ArbitraryFK2: Arbitrary[G ~> H], + ArbitraryFK3: Arbitrary[G ~> F], + ArbitraryFK4: Arbitrary[H ~> G], + EqFA: Eq[T[F, A]], + EqFC: Eq[T[H, A]] + ): RuleSet = { + new DefaultRuleSet( + name = "TFunctor", + parent = None, + "covariant identity" -> forAll(laws.covariantIdentity[F, A] _), + "covariant composition" -> forAll(laws.covariantComposition[F, G, H, A] _)) + } +} + +object TFunctorTests { + def apply[T[_[_], _]: TFunctor]: TFunctorTests[T] = + new TFunctorTests[T] { def laws: TFunctorLaws[T] = TFunctorLaws[T] } +} diff --git a/tests/src/test/scala/cats/tests/EitherKTests.scala b/tests/src/test/scala/cats/tests/EitherKTests.scala index c7b2998adb..a2c43ef7e7 100644 --- a/tests/src/test/scala/cats/tests/EitherKTests.scala +++ b/tests/src/test/scala/cats/tests/EitherKTests.scala @@ -1,6 +1,6 @@ -package cats.tests +package cats +package tests -import cats._ import cats.kernel.laws.OrderLaws import cats.data.EitherK import cats.functor.Contravariant @@ -10,6 +10,9 @@ import cats.laws.discipline.eq._ class EitherKTests extends CatsSuite { + checkAll("EitherK[Option, ?[_], ?]", TFunctorTests[EitherK[Option, ?[_], ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[EitherK[Option, ?[_], ?]]", SerializableTests.serializable(TFunctor[EitherK[Option, ?[_], ?]])) + checkAll("EitherK[Option, Option, ?]", TraverseTests[EitherK[Option, Option, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[EitherK[Option, Option, ?]]", SerializableTests.serializable(Traverse[EitherK[Option, Option, ?]])) diff --git a/tests/src/test/scala/cats/tests/EitherTTests.scala b/tests/src/test/scala/cats/tests/EitherTTests.scala index 01663ee5ac..0f11e765f7 100644 --- a/tests/src/test/scala/cats/tests/EitherTTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTTests.scala @@ -12,6 +12,9 @@ import cats.kernel.laws.{GroupLaws, OrderLaws} class EitherTTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[EitherT[ListWrapper, String, ?]](EitherT.catsDataFunctorForEitherT(ListWrapper.functor)) + checkAll("EitherT[?[_], String, ?]", TFunctorTests[EitherT[?[_], String, ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[EitherT[?[_], String, ?]]", SerializableTests.serializable(TFunctor[EitherT[?[_], String, ?]])) + { checkAll("EitherT[Option, ListWrapper[String], ?]", SemigroupKTests[EitherT[Option, ListWrapper[String], ?]].semigroupK[Int]) checkAll("SemigroupK[EitherT[Option, ListWrapper[String], ?]]", SerializableTests.serializable(SemigroupK[EitherT[Option, ListWrapper[String], ?]])) diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 47f1c7db2f..94f608ad98 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -5,10 +5,14 @@ import cats.data.EitherT import cats.laws.discipline._ import cats.kernel.laws.{GroupLaws, OrderLaws} import scala.util.Try +import cats.laws.discipline.arbitrary._ class EitherTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[Either[Int, ?]] + checkAll("EitherT[?[_], String, ?]", TFunctorTests[EitherT[?[_], String, ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[EitherT[?[_], String, ?]]", SerializableTests.serializable(TFunctor[EitherT[?[_], String, ?]])) + checkAll("Either[String, Int]", GroupLaws[Either[String, Int]].monoid) checkAll("Monoid[Either[String, Int]]", SerializableTests.serializable(Monoid[Either[String, Int]])) diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala index 1f779bd51f..80b4306b34 100644 --- a/tests/src/test/scala/cats/tests/FuncTests.scala +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -17,6 +17,9 @@ class FuncTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[Func[Option, Int, ?]] + checkAll("Func[?[_], String, ?]", TFunctorTests[Func[?[_], String, ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[Func[?[_], String, ?]]", SerializableTests.serializable(TFunctor[Func[?[_], String, ?]])) + checkAll("Func[Option, Int, Int]", CartesianTests[Func[Option, Int, ?]].cartesian[Int, Int, Int]) checkAll("Cartesian[Func[Option, Int, ?]]", SerializableTests.serializable(Cartesian[Func[Option, Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/IdTTests.scala b/tests/src/test/scala/cats/tests/IdTTests.scala index 24a0daf25c..015915e06e 100644 --- a/tests/src/test/scala/cats/tests/IdTTests.scala +++ b/tests/src/test/scala/cats/tests/IdTTests.scala @@ -10,6 +10,9 @@ class IdTTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[IdT[ListWrapper, ?]](IdT.catsDataFunctorForIdT(ListWrapper.functor)) + checkAll("IdT", TFunctorTests[IdT].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[IdT]", SerializableTests.serializable(TFunctor[IdT])) + { implicit val F = ListWrapper.eqv[Option[Int]] diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index c368ec1fb2..6b16598972 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -9,7 +9,7 @@ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import cats.kernel.laws.GroupLaws -import cats.laws.discipline.{SemigroupKTests, MonoidKTests} +import cats.laws.discipline.{MonoidKTests, SemigroupKTests} class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = @@ -23,6 +23,9 @@ class KleisliTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[Kleisli[Option, Int, ?]] implicit val iso2 = CartesianTests.Isomorphisms.invariant[Reader[Int, ?]] + + checkAll("Kleisli[?[_], String, ?]", TFunctorTests[Kleisli[?[_], String, ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[Kleisli[?[_], String, ?]]", SerializableTests.serializable(TFunctor[Kleisli[?[_], String, ?]])) { implicit val instance: ApplicativeError[Kleisli[Option, Int, ?], Unit] = Kleisli.catsDataApplicativeErrorForKleisli[Option, Unit, Int](cats.instances.option.catsStdInstancesForOption) diff --git a/tests/src/test/scala/cats/tests/NestedTests.scala b/tests/src/test/scala/cats/tests/NestedTests.scala index 44f53f63ea..3923d4e691 100644 --- a/tests/src/test/scala/cats/tests/NestedTests.scala +++ b/tests/src/test/scala/cats/tests/NestedTests.scala @@ -16,6 +16,9 @@ class NestedTests extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 20, sizeRange = 5) + checkAll("Nested[?[_], Option, ?]", TFunctorTests[Nested[?[_], Option, ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[Nested[?[_], Option, ?]]", SerializableTests.serializable(TFunctor[Nested[?[_], Option, ?]])) + { // Invariant composition implicit val instance = ListWrapper.invariant diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 8f02a6012e..cc132335a3 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -5,7 +5,7 @@ import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.instances.stream._ import cats.data.{NonEmptyStream, OneAnd} -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} +import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ class OneAndTests extends CatsSuite { @@ -13,6 +13,10 @@ class OneAndTests extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 20, sizeRange = 5) + + checkAll("OneAnd", TFunctorTests[OneAnd].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[OneAnd]", SerializableTests.serializable(TFunctor[OneAnd])) + checkAll("OneAnd[Stream, Int]", OrderLaws[OneAnd[Stream, Int]].eqv) checkAll("OneAnd[Stream, Int] with Option", NonEmptyTraverseTests[OneAnd[Stream, ?]].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 450b4e915e..b417f07fc5 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,14 +1,19 @@ package cats package tests +import cats.TFunctor import cats.data.OptionT import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ + class OptionTTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[OptionT[ListWrapper, ?]](OptionT.catsDataFunctorForOptionT(ListWrapper.functor)) + checkAll("OptionT", TFunctorTests[OptionT].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[OptionT]", SerializableTests.serializable(TFunctor[OptionT])) + { implicit val F = ListWrapper.eqv[Option[Int]] diff --git a/tests/src/test/scala/cats/tests/Tuple2KTests.scala b/tests/src/test/scala/cats/tests/Tuple2KTests.scala index fa041999fa..8dbe2a01c1 100644 --- a/tests/src/test/scala/cats/tests/Tuple2KTests.scala +++ b/tests/src/test/scala/cats/tests/Tuple2KTests.scala @@ -10,6 +10,10 @@ import cats.kernel.laws.OrderLaws class Tuple2KTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[Tuple2K[Option, List, ?]] + + checkAll("Tuple2K[Option, ?[_], ?]", TFunctorTests[Tuple2K[Option, ?[_], ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[Tuple2K[Option, ?[_], ?]]", SerializableTests.serializable(TFunctor[Tuple2K[Option, ?[_], ?]])) + checkAll("Tuple2K[Option, List, Int]", CartesianTests[λ[α => Tuple2K[Option, List, α]]].cartesian[Int, Int, Int]) checkAll("Cartesian[Tuple2K[Option, List, Int]]", SerializableTests.serializable(Cartesian[λ[α => Tuple2K[Option, List, α]]])) diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index cd51848c69..71a77b9701 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -18,6 +18,9 @@ class WriterTTests extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = checkConfiguration.copy(sizeRange = 5) + checkAll("WriterT[?[_], String, ?]", TFunctorTests[WriterT[?[_], String, ?]].tfunctor[List, Vector, Option, Int]) + checkAll("TFunctor[WriterT[?[_], String, ?]]", SerializableTests.serializable(TFunctor[WriterT[?[_], String, ?]])) + checkAll("WriterT[List, Int, Int]", OrderLaws[WriterT[List, Int, Int]].eqv) checkAll("Eq[WriterT[List, Int, Int]]", SerializableTests.serializable(Eq[WriterT[List, Int, Int]]))