From 16ea2edad7496b831ad67838b399838ba84c3ef7 Mon Sep 17 00:00:00 2001 From: "Kai(luo) Wang" Date: Wed, 19 Jul 2017 15:16:30 -0400 Subject: [PATCH] Replace Split with Commutative Arrow. Continuing #1719 (#1766) * Cats-1567 Replace Split with Commutative Arrow This commit removes from the `core` module the `Split` typeclass. This includes the following changes: * The `Arrow` typeclass is no longer a subtype of `Split`. * The `split` operation, previously in the `Split` typeclass, is now integrated in the `Arrow` typeclass, with a default implementation. * Following notation from the `Control.Arrow` library for Haskell, we use the operator `***` as an alias for `split`. * `SplitSyntax` is replaced with `ArrowSyntax`. * We remove the `SplitLaws` and the `SplitTests`. In consequence, ArrowLaws does not inherit from SplitLaws anymore. * We modify the instances for `Kleisli`. We remove the trait `KleisliSpli`, and we replace the _factory_ method that was generating it to generate a `KleisliCompose` instead. * We remove the tests on the `SplitLaws` for Kleisli and CoKleisli * Cats-1567 Add Commutative Arrows We add a type-class of commutative arrows, which are those in which the `split` operation is commutative (can pair in any order). * Cats-1567 Introduce Commutative Monad We introduce a Commutative Monad Type-class, a subclass of Monad in which the flatMap of independent operations is commutative. * Cats-1567 Kleisli Instances We introduce some instances of CommutativeArrow for Kleisli, which are based on CommutativeMonad. * Cats-1567 Split CommutativeMonad into Commutative FlatMap We introduce a new type class, CommutativeFlatMap, to load the commutativity law one level up. * Cats-1567 Commutative Comonad - CoflatMap We introduce the duals of Commutative Comonads and CoflatMap, as the duals of commutative Flatmaps and Monads. This is done to generate and test CommutativeArrow instances for the Cokleisli datatype. * 1567 Complete tests for Cokleisli We complete the tests for the CommutativeArrow instance of Cokleisli. To use it, we mark the Monad instance of `NonEmptyList` as a Commutative Monad. * NonEmptyList comonad is not commutative * fix unmoored statement * added CommutativeMonad instances for Kleisli and WriterT * fix import error * made function1 commutativeArrow * removed CommutativeComonad and CommutativeCoflatmap * added back arrow tests --- .../main/scala/cats/CommutativeFlatMap.scala | 14 ++++ .../main/scala/cats/CommutativeMonad.scala | 14 ++++ core/src/main/scala/cats/arrow/Arrow.scala | 31 +++++++- .../scala/cats/arrow/CommutativeArrow.scala | 13 ++++ core/src/main/scala/cats/arrow/Split.scala | 24 ------ core/src/main/scala/cats/data/Cokleisli.scala | 74 +++++++++++-------- core/src/main/scala/cats/data/Kleisli.scala | 45 ++++++----- core/src/main/scala/cats/data/WriterT.scala | 31 +++++--- .../main/scala/cats/instances/function.scala | 6 +- .../main/scala/cats/instances/option.scala | 4 +- core/src/main/scala/cats/package.scala | 4 +- core/src/main/scala/cats/syntax/all.scala | 2 +- core/src/main/scala/cats/syntax/arrow.scala | 6 ++ core/src/main/scala/cats/syntax/package.scala | 2 +- core/src/main/scala/cats/syntax/split.scala | 6 -- laws/src/main/scala/cats/laws/ArrowLaws.scala | 7 +- .../cats/laws/CommutativeArrowLaws.scala | 22 ++++++ .../cats/laws/CommutativeFlatMapLaws.scala | 19 +++++ .../cats/laws/CommutativeMonadLaws.scala | 14 ++++ laws/src/main/scala/cats/laws/SplitLaws.scala | 22 ------ .../cats/laws/discipline/Arbitrary.scala | 8 +- .../cats/laws/discipline/ArrowTests.scala | 6 +- .../discipline/CommutativeArrowTests.scala | 46 ++++++++++++ .../discipline/CommutativeFlatMapTests.scala | 45 +++++++++++ .../discipline/CommutativeMonadTests.scala | 42 +++++++++++ .../cats/laws/discipline/SplitTests.scala | 31 -------- .../scala/cats/tests/CokleisliTests.scala | 18 +++-- .../test/scala/cats/tests/FunctionTests.scala | 6 +- tests/src/test/scala/cats/tests/IdTests.scala | 4 +- .../test/scala/cats/tests/KleisliTests.scala | 28 +++---- .../scala/cats/tests/NonEmptyListTests.scala | 3 +- .../test/scala/cats/tests/OptionTests.scala | 4 +- .../test/scala/cats/tests/WriterTTests.scala | 3 + 33 files changed, 415 insertions(+), 189 deletions(-) create mode 100644 core/src/main/scala/cats/CommutativeFlatMap.scala create mode 100644 core/src/main/scala/cats/CommutativeMonad.scala create mode 100644 core/src/main/scala/cats/arrow/CommutativeArrow.scala delete mode 100644 core/src/main/scala/cats/arrow/Split.scala create mode 100644 core/src/main/scala/cats/syntax/arrow.scala delete mode 100644 core/src/main/scala/cats/syntax/split.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeArrowLaws.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala delete mode 100644 laws/src/main/scala/cats/laws/SplitLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeArrowTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala delete mode 100644 laws/src/main/scala/cats/laws/discipline/SplitTests.scala diff --git a/core/src/main/scala/cats/CommutativeFlatMap.scala b/core/src/main/scala/cats/CommutativeFlatMap.scala new file mode 100644 index 0000000000..b7293de06b --- /dev/null +++ b/core/src/main/scala/cats/CommutativeFlatMap.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative FlatMap. + * + * Further than a FlatMap, which just allows composition of dependent effectful functions, + * in a Commutative FlatMap those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeFlatMapLaws. + */ +@typeclass trait CommutativeFlatMap[F[_]] extends FlatMap[F] diff --git a/core/src/main/scala/cats/CommutativeMonad.scala b/core/src/main/scala/cats/CommutativeMonad.scala new file mode 100644 index 0000000000..369541142a --- /dev/null +++ b/core/src/main/scala/cats/CommutativeMonad.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative Monad. + * + * Further than a Monad, which just allows composition of dependent effectful functions, + * in a Commutative Monad those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeMonadLaws. + */ +@typeclass trait CommutativeMonad[F[_]] extends Monad[F] with CommutativeFlatMap[F] diff --git a/core/src/main/scala/cats/arrow/Arrow.scala b/core/src/main/scala/cats/arrow/Arrow.scala index 080ea912d4..8ca2659b67 100644 --- a/core/src/main/scala/cats/arrow/Arrow.scala +++ b/core/src/main/scala/cats/arrow/Arrow.scala @@ -5,10 +5,16 @@ import cats.functor.Strong import simulacrum.typeclass -@typeclass trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self => +/** + * Must obey the laws defined in cats.laws.ArrowLaws. + */ +@typeclass trait Arrow[F[_, _]] extends Category[F] with Strong[F] { self => /** - * Lift a function into the context of an Arrow + * Lift a function into the context of an Arrow. + * + * In the reference articles "Arrows are Promiscuous...", and in the corresponding Haskell + * library `Control.Arrow`, this function is called `arr`. */ def lift[A, B](f: A => B): F[A, B] @@ -20,6 +26,25 @@ import simulacrum.typeclass compose(swap, compose(first[A, B, C](fa), swap)) } - override def split[A, B, C, D](f: F[A, B], g: F[C, D]): F[(A, C), (B, D)] = + /** + * Create a new computation `F` that splits its input between `f` and `g` + * and combines the output of each. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import cats.arrow.Arrow + * scala> val toLong: Int => Long = _.toLong + * scala> val toDouble: Float => Double = _.toDouble + * scala> val f: ((Int, Float)) => (Long, Double) = Arrow[Function1].split(toLong, toDouble) + * scala> f((3, 4.0f)) + * res0: (Long, Double) = (3,4.0) + * }}} + * + * Note that the arrow laws do not guarantee the non-interference between the _effects_ of + * `f` and `g` in the context of F. This means that `f *** g` may not be equivalent to `g *** f`. + */ + @simulacrum.op("***", alias = true) + def split[A, B, C, D](f: F[A, B], g: F[C, D]): F[(A, C), (B, D)] = andThen(first(f), second(g)) } diff --git a/core/src/main/scala/cats/arrow/CommutativeArrow.scala b/core/src/main/scala/cats/arrow/CommutativeArrow.scala new file mode 100644 index 0000000000..d074298524 --- /dev/null +++ b/core/src/main/scala/cats/arrow/CommutativeArrow.scala @@ -0,0 +1,13 @@ +package cats +package arrow + +import simulacrum.typeclass + +/** + * In a Commutative Arrow F[_, _], the split operation (or `***`) is commutative, + * which means that there is non-interference between the effect of the paired arrows. + * + * Must obey the laws in CommutativeArrowLaws + */ +@typeclass trait CommutativeArrow[F[_, _]] extends Arrow[F] + diff --git a/core/src/main/scala/cats/arrow/Split.scala b/core/src/main/scala/cats/arrow/Split.scala deleted file mode 100644 index a4313bff46..0000000000 --- a/core/src/main/scala/cats/arrow/Split.scala +++ /dev/null @@ -1,24 +0,0 @@ -package cats -package arrow - -import simulacrum.typeclass - -@typeclass trait Split[F[_, _]] extends Compose[F] { self => - - /** - * Create a new `F` that splits its input between `f` and `g` - * and combines the output of each. - * - * Example: - * {{{ - * scala> import cats.implicits._ - * scala> import cats.arrow.Split - * scala> val toLong: Int => Long = _.toLong - * scala> val toDouble: Float => Double = _.toDouble - * scala> val f: ((Int, Float)) => (Long, Double) = Split[Function1].split(toLong, toDouble) - * scala> f((3, 4.0f)) - * res0: (Long, Double) = (3,4.0) - * }}} - */ - def split[A, B, C, D](f: F[A, B], g: F[C, D]): F[(A, C), (B, D)] -} diff --git a/core/src/main/scala/cats/data/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index 4fdaf0bd37..34a835c16b 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -1,7 +1,7 @@ package cats package data -import cats.arrow.{Arrow, Category, Compose, Split} +import cats.arrow.{Arrow, Category, CommutativeArrow, Compose} import cats.functor.{Contravariant, Profunctor} import cats.{CoflatMap, Comonad, Functor, Monad} import scala.annotation.tailrec @@ -45,37 +45,27 @@ object Cokleisli extends CokleisliInstances { } private[data] sealed abstract class CokleisliInstances extends CokleisliInstances0 { - implicit def catsDataArrowForCokleisli[F[_]](implicit ev: Comonad[F]): Arrow[Cokleisli[F, ?, ?]] = - new CokleisliArrow[F] { def F: Comonad[F] = ev } - implicit def catsDataMonadForCokleisli[F[_], A]: Monad[Cokleisli[F, A, ?]] = new Monad[Cokleisli[F, A, ?]] { - def pure[B](x: B): Cokleisli[F, A, B] = - Cokleisli.pure(x) - - def flatMap[B, C](fa: Cokleisli[F, A, B])(f: B => Cokleisli[F, A, C]): Cokleisli[F, A, C] = - fa.flatMap(f) - - override def map[B, C](fa: Cokleisli[F, A, B])(f: B => C): Cokleisli[F, A, C] = - fa.map(f) - - def tailRecM[B, C](b: B)(fn: B => Cokleisli[F, A, Either[B, C]]): Cokleisli[F, A, C] = - Cokleisli({ (fa: F[A]) => - @tailrec - def loop(c: Cokleisli[F, A, Either[B, C]]): C = c.run(fa) match { - case Right(c) => c - case Left(bb) => loop(fn(bb)) - } - loop(fn(b)) - }) + implicit val catsDataCommutativeArrowForCokleisliId: CommutativeArrow[Cokleisli[Id, ?, ?]] = + new CokleisliArrow[Id] with CommutativeArrow[Cokleisli[Id, ?, ?]] { + def F: Comonad[Id] = Comonad[Id] } + implicit def catsDataMonadForCokleisli[F[_], A]: Monad[Cokleisli[F, A, ?]] = + new CokleisliMonad[F, A] + implicit def catsDataMonoidKForCokleisli[F[_]](implicit ev: Comonad[F]): MonoidK[λ[α => Cokleisli[F, α, α]]] = Category[Cokleisli[F, ?, ?]].algebraK } -private[data] sealed abstract class CokleisliInstances0 { - implicit def catsDataSplitForCokleisli[F[_]](implicit ev: CoflatMap[F]): Split[Cokleisli[F, ?, ?]] = - new CokleisliSplit[F] { def F: CoflatMap[F] = ev } +private[data] sealed abstract class CokleisliInstances0 extends CokleisliInstances1 { + implicit def catsDataArrowForCokleisli[F[_]](implicit ev: Comonad[F]): Arrow[Cokleisli[F, ?, ?]] = + new CokleisliArrow[F] { def F: Comonad[F] = ev } +} + +private[data] sealed abstract class CokleisliInstances1 { + implicit def catsDataComposeForCokleisli[F[_]](implicit ev: CoflatMap[F]): Compose[Cokleisli[F, ?, ?]] = + new CokleisliCompose[F] { def F: CoflatMap[F] = ev } implicit def catsDataProfunctorForCokleisli[F[_]](implicit ev: Functor[F]): Profunctor[Cokleisli[F, ?, ?]] = new CokleisliProfunctor[F] { def F: Functor[F] = ev } @@ -89,7 +79,32 @@ private[data] sealed abstract class CokleisliInstances0 { } } -private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliSplit[F] with CokleisliProfunctor[F] { + + +private[data] class CokleisliMonad[F[_], A] extends Monad[Cokleisli[F, A, ?]] { + + def pure[B](x: B): Cokleisli[F, A, B] = + Cokleisli.pure(x) + + def flatMap[B, C](fa: Cokleisli[F, A, B])(f: B => Cokleisli[F, A, C]): Cokleisli[F, A, C] = + fa.flatMap(f) + + override def map[B, C](fa: Cokleisli[F, A, B])(f: B => C): Cokleisli[F, A, C] = + fa.map(f) + + def tailRecM[B, C](b: B)(fn: B => Cokleisli[F, A, Either[B, C]]): Cokleisli[F, A, C] = + Cokleisli({ (fa: F[A]) => + @tailrec + def loop(c: Cokleisli[F, A, Either[B, C]]): C = c.run(fa) match { + case Right(c) => c + case Left(bb) => loop(fn(bb)) + } + loop(fn(b)) + }) + +} + +private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliCompose[F] with CokleisliProfunctor[F] { implicit def F: Comonad[F] def lift[A, B](f: A => B): Cokleisli[F, A, B] = @@ -108,17 +123,14 @@ private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with Coklei super[CokleisliProfunctor].dimap(fab)(f)(g) override def split[A, B, C, D](f: Cokleisli[F, A, B], g: Cokleisli[F, C, D]): Cokleisli[F, (A, C), (B, D)] = - super[CokleisliSplit].split(f, g) + Cokleisli(fac => f.run(F.map(fac)(_._1)) -> g.run(F.map(fac)(_._2))) } -private trait CokleisliSplit[F[_]] extends Split[Cokleisli[F, ?, ?]] { +private trait CokleisliCompose[F[_]] extends Compose[Cokleisli[F, ?, ?]] { implicit def F: CoflatMap[F] def compose[A, B, C](f: Cokleisli[F, B, C], g: Cokleisli[F, A, B]): Cokleisli[F, A, C] = f.compose(g) - - def split[A, B, C, D](f: Cokleisli[F, A, B], g: Cokleisli[F, C, D]): Cokleisli[F, (A, C), (B, D)] = - Cokleisli(fac => f.run(F.map(fac)(_._1)) -> g.run(F.map(fac)(_._2))) } private trait CokleisliProfunctor[F[_]] extends Profunctor[Cokleisli[F, ?, ?]] { diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index c9ab66983b..bec95e659f 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -1,7 +1,7 @@ package cats package data -import cats.arrow.{Arrow, Category, Choice, Compose, Split, FunctionK} +import cats.arrow.{Arrow, Category, Choice, CommutativeArrow, Compose, FunctionK} import cats.functor.{Contravariant, Strong} /** @@ -81,7 +81,13 @@ private[data] sealed trait KleisliFunctions { } private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { + 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 + } +} +private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { implicit def catsDataMonoidForKleisli[F[_], A, B](implicit FB0: Monoid[F[B]]): Monoid[Kleisli[F, A, B]] = new KleisliMonoid[F, A, B] { def FB: Monoid[F[B]] = FB0 } @@ -91,11 +97,11 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { implicit val catsDataMonoidKForKleisliId: MonoidK[λ[α => Kleisli[Id, α, α]]] = catsDataMonoidKForKleisli[Id] - implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] = - new KleisliArrow[F] { def F: Monad[F] = M } + implicit def catsDataCommutativeArrowForKleisli[F[_]](implicit M: CommutativeMonad[F]): CommutativeArrow[Kleisli[F, ?, ?]] = + new KleisliCommutativeArrow[F] {def F: CommutativeMonad[F] = M } - implicit val catsDataArrowForKleisliId: Arrow[Kleisli[Id, ?, ?]] = - catsDataArrowForKleisli[Id] + implicit val catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] = + catsDataCommutativeArrowForKleisli[Id] implicit def catsDataMonadReaderForKleisliId[A]: MonadReader[Kleisli[Id, A, ?], A] = catsDataMonadReaderForKleisli[Id, A] @@ -115,25 +121,28 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { new KleisliApplicativeError[F, A, E] { def F: ApplicativeError[F, E] = AE } } -private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { +private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 { + implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] = + new KleisliArrow[F] { def F: Monad[F] = M } + implicit def catsDataMonadErrorForKleisli[F[_], A, E](implicit ME: MonadError[F, E]): MonadError[Kleisli[F, A, ?], E] = new KleisliMonadError[F, A, E] { def F: MonadError[F, E] = ME } } -private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 { +private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 { implicit def catsDataMonadReaderForKleisli[F[_], A](implicit M: Monad[F]): MonadReader[Kleisli[F, A, ?], A] = new KleisliMonadReader[F, A] { def F: Monad[F] = M } } -private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 { +private[data] sealed abstract class KleisliInstances3 extends KleisliInstances4 { implicit def catsDataChoiceForKleisli[F[_]](implicit M: Monad[F]): Choice[Kleisli[F, ?, ?]] = new KleisliChoice[F] { def F: Monad[F] = M } implicit val catsDataChoiceForKleisliId: Choice[Kleisli[Id, ?, ?]] = catsDataChoiceForKleisli[Id] - implicit def catsDataSplitForKleisli[F[_]](implicit FM: FlatMap[F]): Split[Kleisli[F, ?, ?]] = - new KleisliSplit[F] { def F: FlatMap[F] = FM } + implicit def catsDataComposeForKleisli[F[_]](implicit FM: FlatMap[F]): Compose[Kleisli[F, ?, ?]] = + new KleisliCompose[F] { def F: FlatMap[F] = FM } implicit def catsDataStrongForKleisli[F[_]](implicit F0: Functor[F]): Strong[Kleisli[F, ?, ?]] = new KleisliStrong[F] { def F: Functor[F] = F0 } @@ -148,30 +157,30 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 Compose[Kleisli[F, ?, ?]].algebraK } -private[data] sealed abstract class KleisliInstances3 extends KleisliInstances4 { +private[data] sealed abstract class KleisliInstances4 extends KleisliInstances5 { implicit def catsDataApplicativeForKleisli[F[_], A](implicit A: Applicative[F]): Applicative[Kleisli[F, A, ?]] = new KleisliApplicative[F, A] { def F: Applicative[F] = A } } -private[data] sealed abstract class KleisliInstances4 extends KleisliInstances5 { +private[data] sealed abstract class KleisliInstances5 extends KleisliInstances6 { implicit def catsDataApplyForKleisli[F[_], A](implicit A: Apply[F]): Apply[Kleisli[F, A, ?]] = new KleisliApply[F, A] { def F: Apply[F] = A } } -private[data] sealed abstract class KleisliInstances5 { +private[data] sealed abstract class KleisliInstances6 { implicit def catsDataFunctorForKleisli[F[_], A](implicit F0: Functor[F]): Functor[Kleisli[F, A, ?]] = new KleisliFunctor[F, A] { def F: Functor[F] = F0 } } -private trait KleisliArrow[F[_]] extends Arrow[Kleisli[F, ?, ?]] with KleisliSplit[F] with KleisliStrong[F] with KleisliCategory[F] { +private trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleisli[F, ?, ?]] with KleisliArrow[F] { + implicit def F: CommutativeMonad[F] +} + +private trait KleisliArrow[F[_]] extends Arrow[Kleisli[F, ?, ?]] with KleisliCategory[F] with KleisliStrong[F] { implicit def F: Monad[F] def lift[A, B](f: A => B): Kleisli[F, A, B] = Kleisli(a => F.pure(f(a))) -} - -private trait KleisliSplit[F[_]] extends Split[Kleisli[F, ?, ?]] with KleisliCompose[F] { - implicit def F: FlatMap[F] override def split[A, B, C, D](f: Kleisli[F, A, B], g: Kleisli[F, C, D]): Kleisli[F, (A, C), (B, D)] = Kleisli{ case (a, c) => F.flatMap(f.run(a))(b => F.map(g.run(c))(d => (b, d))) } diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index c3cce1398b..ee2e5a878f 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -3,6 +3,7 @@ package data import cats.kernel.instances.tuple._ import cats.functor.{Bifunctor, Contravariant} +import cats.kernel.CommutativeMonoid import cats.syntax.semigroup._ final case class WriterT[F[_], L, V](run: F[(L, V)]) { @@ -66,6 +67,14 @@ object WriterT extends WriterTInstances with WriterTFunctions { } private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { + 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 + implicit val L0: Monoid[L] = L + } +} + +private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { implicit def catsDataMonadForWriterTId[L:Monoid]: Monad[WriterT[Id, L, ?]] = catsDataMonadWriterForWriterT[Id, L] @@ -93,7 +102,7 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { catsDataMonoidForWriterT[Id, L, V] } -private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { +private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { implicit def catsDataMonadCombineForWriterT[F[_], L](implicit F: MonadCombine[F], L: Monoid[L]): MonadCombine[WriterT[F, L, ?]] = new WriterTMonadCombine[F, L] { implicit val F0: MonadCombine[F] = F @@ -110,7 +119,7 @@ private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 catsDataSemigroupForWriterT[Id, L, V] } -private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { +private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { implicit def catsDataMonadFilterForWriterT[F[_], L](implicit F: MonadFilter[F], L: Monoid[L]): MonadFilter[WriterT[F, L, ?]] = new WriterTMonadFilter[F, L] { implicit val F0: MonadFilter[F] = F @@ -126,7 +135,7 @@ private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 catsDataCoflatMapForWriterT[Id, L] } -private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { +private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { implicit def catsDataMonadWriterForWriterT[F[_], L](implicit F: Monad[F], L: Monoid[L]): MonadWriter[WriterT[F, L, ?], L] = new WriterTMonadWriter[F, L] { implicit val F0: Monad[F] = F @@ -139,7 +148,7 @@ private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 } } -private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { +private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 { implicit def catsDataAlternativeForWriterT[F[_], L](implicit F: Alternative[F], L: Monoid[L]): Alternative[WriterT[F, L, ?]] = new WriterTAlternative[F, L] { implicit val F0: Alternative[F] = F @@ -148,7 +157,7 @@ private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 } -private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 { +private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 { implicit def catsDataApplicativeForWriterT[F[_], L](implicit F: Applicative[F], L: Monoid[L]): Applicative[WriterT[F, L, ?]] = new WriterTApplicative[F, L] { implicit val F0: Applicative[F] = F @@ -161,7 +170,7 @@ private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 } } -private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 { +private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 { implicit def catsDataFlatMapForWriterT1[F[_], L](implicit F: FlatMap[F], L: Monoid[L]): FlatMap[WriterT[F, L, ?]] = new WriterTFlatMap1[F, L] { implicit val F0: FlatMap[F] = F @@ -174,7 +183,7 @@ private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 } } -private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 { +private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 { implicit def catsDataFlatMapForWriterT2[F[_], L](implicit F: Monad[F], L: Semigroup[L]): FlatMap[WriterT[F, L, ?]] = new WriterTFlatMap2[F, L] { implicit val F0: Monad[F] = F @@ -182,7 +191,7 @@ private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 } } -private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 { +private[data] sealed abstract class WriterTInstances8 extends WriterTInstances9 { implicit def catsDataApplyForWriterT[F[_], L](implicit F: Apply[F], L: Semigroup[L]): Apply[WriterT[F, L, ?]] = new WriterTApply[F, L] { @@ -191,7 +200,7 @@ private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 } } -private[data] sealed abstract class WriterTInstances8 extends WriterTInstances9 { +private[data] sealed abstract class WriterTInstances9 extends WriterTInstances10 { implicit def catsDataCoflatMapForWriterT[F[_], L](implicit F: Functor[F]): CoflatMap[WriterT[F, L, ?]] = new WriterTCoflatMap[F, L] { implicit val F0: Functor[F] = F @@ -202,7 +211,7 @@ private[data] sealed abstract class WriterTInstances8 extends WriterTInstances9 } } -private[data] sealed abstract class WriterTInstances9 extends WriterTInstances10 { +private[data] sealed abstract class WriterTInstances10 extends WriterTInstances11 { implicit def catsDataMonadErrorForWriterT[F[_], L, E](implicit F: MonadError[F, E], L: Monoid[L]): MonadError[WriterT[F, L, ?], E] = new WriterTMonadError[F, L, E] { implicit val F0: MonadError[F, E] = F @@ -210,7 +219,7 @@ private[data] sealed abstract class WriterTInstances9 extends WriterTInstances10 } } -private[data] sealed abstract class WriterTInstances10 { +private[data] sealed abstract class WriterTInstances11 { implicit def catsDataApplicativeErrorForWriterT[F[_], L, E](implicit F: ApplicativeError[F, E], L: Monoid[L]): ApplicativeError[WriterT[F, L, ?], E] = new WriterTApplicativeError[F, L, E] { implicit val F0: ApplicativeError[F, E] = F diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 36a239e2de..3aa53cb3df 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -1,7 +1,7 @@ package cats package instances -import cats.arrow.{Arrow, Category, Choice} +import cats.arrow.{CommutativeArrow, Category, Choice} import cats.functor.Contravariant import annotation.tailrec @@ -67,8 +67,8 @@ private[instances] sealed trait Function1Instances { } } - implicit val catsStdInstancesForFunction1: Choice[Function1] with Arrow[Function1] = - new Choice[Function1] with Arrow[Function1] { + implicit val catsStdInstancesForFunction1: Choice[Function1] with CommutativeArrow[Function1] = + new Choice[Function1] with CommutativeArrow[Function1] { def choice[A, B, C](f: A => C, g: B => C): Either[A, B] => C = { case Left(a) => f(a) case Right(b) => g(b) diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index 49c3cf3690..be654f0d3a 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -5,8 +5,8 @@ import scala.annotation.tailrec trait OptionInstances extends cats.kernel.instances.OptionInstances { - implicit val catsStdInstancesForOption: TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with Monad[Option] with CoflatMap[Option] with Alternative[Option] = - new TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with Monad[Option] with CoflatMap[Option] with Alternative[Option] { + implicit val catsStdInstancesForOption: TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with CommutativeMonad[Option] with CoflatMap[Option] with Alternative[Option] = + new TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with CommutativeMonad[Option] with CoflatMap[Option] with Alternative[Option] { def empty[A]: Option[A] = None diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 88f71bae38..59fab09423 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -32,8 +32,8 @@ package object cats { * encodes pure unary function application. */ type Id[A] = A - implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] = - new Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] { + implicit val catsInstancesForId: Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] = + new Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] { def pure[A](a: A): A = a def extract[A](a: A): A = a def flatMap[A, B](a: A)(f: A => B): B = f(a) diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 5352b51183..67d4b2c235 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -5,6 +5,7 @@ trait AllSyntax extends ApplicativeSyntax with ApplicativeErrorSyntax with ApplySyntax + with ArrowSyntax with BifunctorSyntax with BifoldableSyntax with BitraverseSyntax @@ -38,7 +39,6 @@ trait AllSyntax with SemigroupSyntax with SemigroupKSyntax with ShowSyntax - with SplitSyntax with StrongSyntax with TraverseFilterSyntax with TraverseSyntax diff --git a/core/src/main/scala/cats/syntax/arrow.scala b/core/src/main/scala/cats/syntax/arrow.scala new file mode 100644 index 0000000000..4498e36d85 --- /dev/null +++ b/core/src/main/scala/cats/syntax/arrow.scala @@ -0,0 +1,6 @@ +package cats +package syntax + +import cats.arrow.Arrow + +trait ArrowSyntax extends Arrow.ToArrowOps diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 36ab4bf511..44abb61a08 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -5,6 +5,7 @@ package object syntax { object applicative extends ApplicativeSyntax object applicativeError extends ApplicativeErrorSyntax object apply extends ApplySyntax + object arrow extends ArrowSyntax object bifunctor extends BifunctorSyntax object bifoldable extends BifoldableSyntax object bitraverse extends BitraverseSyntax @@ -37,7 +38,6 @@ package object syntax { object semigroup extends SemigroupSyntax object semigroupk extends SemigroupKSyntax object show extends ShowSyntax - object split extends SplitSyntax object strong extends StrongSyntax object monadTrans extends MonadTransSyntax object traverse extends TraverseSyntax diff --git a/core/src/main/scala/cats/syntax/split.scala b/core/src/main/scala/cats/syntax/split.scala deleted file mode 100644 index 95d3202506..0000000000 --- a/core/src/main/scala/cats/syntax/split.scala +++ /dev/null @@ -1,6 +0,0 @@ -package cats -package syntax - -import cats.arrow.Split - -trait SplitSyntax extends Split.ToSplitOps diff --git a/laws/src/main/scala/cats/laws/ArrowLaws.scala b/laws/src/main/scala/cats/laws/ArrowLaws.scala index 80213ac434..f02a8e47ed 100644 --- a/laws/src/main/scala/cats/laws/ArrowLaws.scala +++ b/laws/src/main/scala/cats/laws/ArrowLaws.scala @@ -3,14 +3,14 @@ package laws import cats.arrow.Arrow import cats.instances.function._ +import cats.syntax.arrow._ import cats.syntax.compose._ -import cats.syntax.split._ import cats.syntax.strong._ /** * Laws that must be obeyed by any `cats.arrow.Arrow`. */ -trait ArrowLaws[F[_, _]] extends CategoryLaws[F] with SplitLaws[F] with StrongLaws[F] { +trait ArrowLaws[F[_, _]] extends CategoryLaws[F] with StrongLaws[F] { implicit override def F: Arrow[F] def arrowIdentity[A]: IsEq[F[A, A]] = @@ -34,6 +34,9 @@ trait ArrowLaws[F[_, _]] extends CategoryLaws[F] with SplitLaws[F] with StrongLa def arrowAssociation[A, B, C, D](f: F[A, B]): IsEq[F[((A, C), D), (B, (C, D))]] = (f.first[C].first[D] andThen F.lift(assoc[B, C, D])) <-> (F.lift(assoc[A, C, D]) andThen f.first[(C, D)]) + def splitConsistentWithAndThen[A, B, C, D](f: F[A, B], g: F[C, D]): IsEq[F[(A, C), (B, D)]] = + F.split(f, g) <-> (f.first andThen g.second) + private def fst[A, B](p: (A, B)): A = p._1 private def assoc[A, B, C](p: ((A, B), C)): (A, (B, C)) = (p._1._1, (p._1._2, p._2)) diff --git a/laws/src/main/scala/cats/laws/CommutativeArrowLaws.scala b/laws/src/main/scala/cats/laws/CommutativeArrowLaws.scala new file mode 100644 index 0000000000..9212b896eb --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeArrowLaws.scala @@ -0,0 +1,22 @@ +package cats +package laws + +import cats.arrow.CommutativeArrow +import cats.syntax.compose._ +import cats.syntax.strong._ + +/** Reference: "Causal Commutative Arrows", Journal of Functional Programming + * Figure 4. + */ +trait CommutativeArrowLaws[F[_, _]] extends ArrowLaws[F] { + implicit override def F: CommutativeArrow[F] + + def arrowCommutative[A, B, C, D](f: F[A, B], g: F[C, D]): IsEq[F[(A, C), (B, D)]] = + (f.first[C] >>> g.second[B]) <-> (g.second[A] >>> f.first[D]) + +} + +object CommutativeArrowLaws { + def apply[F[_, _]](implicit ev: CommutativeArrow[F]): CommutativeArrowLaws[F] = + new CommutativeArrowLaws[F] { def F: CommutativeArrow[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala b/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala new file mode 100644 index 0000000000..321183097c --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala @@ -0,0 +1,19 @@ +package cats +package laws + +/** + * Laws that must be obeyed by any `CommutativeFlatMap`. + */ +trait CommutativeFlatMapLaws[F[_]] extends FlatMapLaws[F] { + implicit override def F: CommutativeFlatMap[F] + + def flatmapCommutative[A, B, C](fa: F[A], fb: F[B], g: (A, B) => F[C]): IsEq[F[C]] = + F.flatMap(fa)( a => F.flatMap(fb)( b => g(a, b))) <-> + F.flatMap(fb)( b => F.flatMap(fa)( a => g(a, b))) + +} + +object CommutativeFlatMapLaws { + def apply[F[_]](implicit ev: CommutativeFlatMap[F]): CommutativeFlatMapLaws[F] = + new CommutativeFlatMapLaws[F] { def F: CommutativeFlatMap[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala new file mode 100644 index 0000000000..1172b30165 --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala @@ -0,0 +1,14 @@ +package cats +package laws + +/** + * Laws that must be obeyed by any `CommutativeMonad`. + */ +trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] with CommutativeFlatMapLaws[F] { + implicit override def F: CommutativeMonad[F] +} + +object CommutativeMonadLaws { + def apply[F[_]](implicit ev: CommutativeMonad[F]): CommutativeMonadLaws[F] = + new CommutativeMonadLaws[F] { def F: CommutativeMonad[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/SplitLaws.scala b/laws/src/main/scala/cats/laws/SplitLaws.scala deleted file mode 100644 index b04745519c..0000000000 --- a/laws/src/main/scala/cats/laws/SplitLaws.scala +++ /dev/null @@ -1,22 +0,0 @@ -package cats -package laws - -import cats.arrow.Split -import cats.syntax.compose._ -import cats.syntax.split._ - -/** - * Laws that must be obeyed by any `cats.arrow.Split`. - */ -trait SplitLaws[F[_, _]] extends ComposeLaws[F] { - implicit override def F: Split[F] - - def splitInterchange[A1, A2, A3, B1, B2, B3](f1: F[A1, A2], f2: F[A2, A3], - g1: F[B1, B2], g2: F[B2, B3]): IsEq[F[(A1, B1), (A3, B3)]] = - ((f1 split g1) andThen (f2 split g2)) <-> ((f1 andThen f2) split (g1 andThen g2)) -} - -object SplitLaws { - def apply[F[_, _]](implicit ev: Split[F]): SplitLaws[F] = - new SplitLaws[F] { def F: Split[F] = ev } -} diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index fe0173fdd4..1cdb684e8d 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -76,8 +76,6 @@ object arbitrary extends ArbitraryInstances0 { B.perturb(seed, _), (a, b) => A.perturb(B.perturb(seed, b), a))) - implicit def catsLawsArbitraryForCokleisli[F[_], A, B](implicit AFA: Arbitrary[F[A]], CFA: Cogen[F[A]], B: Arbitrary[B]): Arbitrary[Cokleisli[F, A, B]] = - Arbitrary(Arbitrary.arbitrary[F[A] => B].map(Cokleisli(_))) implicit def catsLawsArbitraryForOptionT[F[_], A](implicit F: Arbitrary[F[Option[A]]]): Arbitrary[OptionT[F, A]] = Arbitrary(F.arbitrary.map(OptionT.apply)) @@ -161,6 +159,10 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawArbitraryForReader[A: Arbitrary: Cogen, B: Arbitrary]: Arbitrary[Reader[A, B]] = catsLawsArbitraryForKleisli[Id, A, B] + + implicit def catsLawArbitraryForCokleisliId[A: Arbitrary: Cogen, B: Arbitrary]: Arbitrary[Cokleisli[Id, A, B]] = + catsLawsArbitraryForCokleisli[Id, A, B] + 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(_))) } @@ -179,4 +181,6 @@ private[discipline] sealed trait ArbitraryInstances0 { implicit def catsLawsArbitraryForKleisli[F[_], A, B](implicit AA: Arbitrary[A], CA: Cogen[A], F: Arbitrary[F[B]]): Arbitrary[Kleisli[F, A, B]] = Arbitrary(Arbitrary.arbitrary[A => F[B]].map(Kleisli(_))) + implicit def catsLawsArbitraryForCokleisli[F[_], A, B](implicit AFA: Arbitrary[F[A]], CFA: Cogen[F[A]], B: Arbitrary[B]): Arbitrary[Cokleisli[F, A, B]] = + Arbitrary(Arbitrary.arbitrary[F[A] => B].map(Cokleisli(_))) } diff --git a/laws/src/main/scala/cats/laws/discipline/ArrowTests.scala b/laws/src/main/scala/cats/laws/discipline/ArrowTests.scala index 0a133a106a..67274761ae 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArrowTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArrowTests.scala @@ -6,7 +6,7 @@ import cats.arrow.Arrow import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait ArrowTests[F[_, _]] extends CategoryTests[F] with SplitTests[F] with StrongTests[F] { +trait ArrowTests[F[_, _]] extends CategoryTests[F] with StrongTests[F] { def laws: ArrowLaws[F] def arrow[A: Arbitrary, B: Arbitrary, C: Arbitrary, D: Arbitrary, E: Arbitrary, G: Arbitrary](implicit @@ -39,7 +39,6 @@ trait ArrowTests[F[_, _]] extends CategoryTests[F] with SplitTests[F] with Stron def bases: Seq[(String, RuleSet)] = Nil def parents: Seq[RuleSet] = Seq( category[A, B, C, D], - split[A, B, C, D, E, G], strong[A, B, C, D, E, G] ) def props: Seq[(String, Prop)] = Seq( @@ -49,7 +48,8 @@ trait ArrowTests[F[_, _]] extends CategoryTests[F] with SplitTests[F] with Stron "arrow functor" -> forAll(laws.arrowFunctor[A, B, C, D] _), "arrow exchange" -> forAll(laws.arrowExchange[A, B, C, D] _), "arrow unit" -> forAll(laws.arrowUnit[A, B, C] _), - "arrow association" -> forAll(laws.arrowAssociation[A, B, C, D] _) + "arrow association" -> forAll(laws.arrowAssociation[A, B, C, D] _), + "split consistent with andThen" -> forAll(laws.splitConsistentWithAndThen[A, B, C, D] _) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeArrowTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeArrowTests.scala new file mode 100644 index 0000000000..93c0742e41 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeArrowTests.scala @@ -0,0 +1,46 @@ +package cats +package laws +package discipline + +import cats.arrow.CommutativeArrow +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait CommutativeArrowTests[F[_, _]] extends ArrowTests[F] { + def laws: CommutativeArrowLaws[F] + + def commutativeArrow[A: Arbitrary, B: Arbitrary, C: Arbitrary, D: Arbitrary, E: Arbitrary, G: Arbitrary](implicit + ArbFAB: Arbitrary[F[A, B]], + ArbFBC: Arbitrary[F[B, C]], + ArbFCD: Arbitrary[F[C, D]], + ArbFDE: Arbitrary[F[D, E]], + ArbFEG: Arbitrary[F[E, G]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + CogenD: Cogen[D], + CogenE: Cogen[E], + EqFAA: Eq[F[A, A]], + EqFAB: Eq[F[A, B]], + EqFAC: Eq[F[A, C]], + EqFAD: Eq[F[A, D]], + EqFAG: Eq[F[A, G]], + EqFACB: Eq[F[(A, C), B]], + EqFACBC: Eq[F[(A, C), (B, C)]], + EqFACBD: Eq[F[(A, C), (B, D)]], + EqFADCD: Eq[F[(A, D), (C, D)]], + EqFADCG: Eq[F[(A, D), (C, G)]], + EqFAEDE: Eq[F[(A, E), (D, E)]], + EqFEAED: Eq[F[(E, A), (E, D)]], + EqFACDBCD: Eq[F[((A, C), D), (B, (C, D))]] + ): RuleSet = + new DefaultRuleSet( + name = "commutative arrow", + parent = Some(arrow[A, B, C, D, E, G]), + "arrow commutativity" -> forAll(laws.arrowCommutative[A, B, C, D] _)) +} + +object CommutativeArrowTests { + def apply[F[_, _]: CommutativeArrow]: CommutativeArrowTests[F] = + new CommutativeArrowTests[F] { def laws: CommutativeArrowLaws[F] = CommutativeArrowLaws[F] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala new file mode 100644 index 0000000000..70a69fe2bd --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala @@ -0,0 +1,45 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.CartesianTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait CommutativeFlatMapTests[F[_]] extends FlatMapTests[F] { + def laws: CommutativeFlatMapLaws[F] + + def commutativeFlatMap[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => 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)]], + EqFInt: Eq[F[Int]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + def name: String = "commutative flatMap" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(flatMap[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "flatmap commutativity" -> forAll(laws.flatmapCommutative[A, B, C] _) + ) + } + } + +} + +object CommutativeFlatMapTests { + def apply[F[_]: CommutativeFlatMap]: CommutativeFlatMapTests[F] = + new CommutativeFlatMapTests[F] { + def laws: CommutativeFlatMapLaws[F] = CommutativeFlatMapLaws[F] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala new file mode 100644 index 0000000000..c2a0975759 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala @@ -0,0 +1,42 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.CartesianTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} + +trait CommutativeMonadTests[F[_]] extends MonadTests[F] with CommutativeFlatMapTests[F] { + def laws: CommutativeMonadLaws[F] + + def commutativeMonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => 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)]], + EqFInt: Eq[F[Int]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + def name: String = "commutative monad" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monad[A, B, C], commutativeFlatMap[A, B, C]) + def props: Seq[(String, Prop)] = Nil + } + } + +} + +object CommutativeMonadTests { + def apply[F[_]: CommutativeMonad]: CommutativeMonadTests[F] = + new CommutativeMonadTests[F] { + def laws: CommutativeMonadLaws[F] = CommutativeMonadLaws[F] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/SplitTests.scala b/laws/src/main/scala/cats/laws/discipline/SplitTests.scala deleted file mode 100644 index 3b11c2e3a9..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/SplitTests.scala +++ /dev/null @@ -1,31 +0,0 @@ -package cats -package laws -package discipline - -import cats.arrow.Split -import org.scalacheck.Arbitrary -import org.scalacheck.Prop -import Prop._ - -trait SplitTests[F[_, _]] extends ComposeTests[F] { - def laws: SplitLaws[F] - - def split[A, B, C, D, E, G](implicit - ArbFAB: Arbitrary[F[A, B]], - ArbFBC: Arbitrary[F[B, C]], - ArbFCD: Arbitrary[F[C, D]], - ArbFDE: Arbitrary[F[D, E]], - ArbFEG: Arbitrary[F[E, G]], - EqFAD: Eq[F[A, D]], - EqFADCG: Eq[F[(A, D), (C, G)]] - ): RuleSet = - new DefaultRuleSet( - name = "split", - parent = Some(compose[A, B, C, D]), - "split interchange" -> forAll(laws.splitInterchange[A, B, C, D, E, G] _)) -} - -object SplitTests { - def apply[F[_, _]: Split]: SplitTests[F] = - new SplitTests[F] { def laws: SplitLaws[F] = SplitLaws[F] } -} diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index 214c02999b..837c4c747e 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Arrow, Split} +import cats.arrow.{Arrow, CommutativeArrow} import cats.data.{Cokleisli, NonEmptyList} import cats.functor.{Contravariant, Profunctor} import cats.laws.discipline._ @@ -33,14 +33,9 @@ class CokleisliTests extends SlowCatsSuite { checkAll("Cokleisli[Option, Int, Int]", ProfunctorTests[Cokleisli[Option, ?, ?]].profunctor[Int, Int, Int, Int, Int, Int]) checkAll("Profunctor[Cokleisli[Option, ?, ?]]", SerializableTests.serializable(Profunctor[Cokleisli[Option, ?, ?]])) - checkAll("Cokleisli[Option, Int, Int]", SplitTests[Cokleisli[Option, ?, ?]].split[Int, Int, Int, Int, Int, Int]) - checkAll("Split[Cokleisli[Option, ?, ?]]", SerializableTests.serializable(Split[Cokleisli[Option, ?, ?]])) - checkAll("Cokleisli[Option, Int, Int]", ContravariantTests[Cokleisli[Option, ?, Int]].contravariant[Int, Int, Int]) checkAll("Contravariant[Cokleisli[Option, ?, Int]]", SerializableTests.serializable(Contravariant[Cokleisli[Option, ?, Int]])) - checkAll("Cokleisli[NonEmptyList, Int, Int]", ArrowTests[Cokleisli[NonEmptyList, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) - checkAll("Arrow[Cokleisli[NonEmptyList, ?, ?]]", SerializableTests.serializable(Arrow[Cokleisli[NonEmptyList, ?, ?]])) checkAll("Cokleisli[NonEmptyList, Int, Int]", MonoidKTests[λ[α => Cokleisli[NonEmptyList, α, α]]].monoidK[Int]) checkAll("MonoidK[λ[α => Cokleisli[NonEmptyList, α, α]]]", SerializableTests.serializable(MonoidK[λ[α => Cokleisli[NonEmptyList, α, α]]])) @@ -48,6 +43,17 @@ class CokleisliTests extends SlowCatsSuite { checkAll("Cokleisli[List, Int, Int]", SemigroupKTests[λ[α => Cokleisli[List, α, α]]].semigroupK[Int]) checkAll("SemigroupK[λ[α => Cokleisli[List, α, α]]]", SerializableTests.serializable(SemigroupK[λ[α => Cokleisli[List, α, α]]])) + checkAll("Cokleisli[NonEmptyList, Int, Int]", ArrowTests[Cokleisli[NonEmptyList, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) + checkAll("Arrow[Cokleisli[NonEmptyList, ?, ?]]", SerializableTests.serializable(Arrow[Cokleisli[NonEmptyList, ?, ?]])) + + { + implicit def cokleisliIdEq[A, B](implicit A: Arbitrary[A], FB: Eq[B]): Eq[Cokleisli[Id, A, B]] = + Eq.by[Cokleisli[Id, A, B], A => B](_.run) + + checkAll("Cokleisli[Id, Int, Int]", CommutativeArrowTests[Cokleisli[Id, ?, ?]].commutativeArrow[Int, Int, Int, Int, Int, Int]) + checkAll("CommutativeArrow[Cokleisli[Id, ?, ?]]", SerializableTests.serializable(CommutativeArrow[Cokleisli[Id, ?, ?]])) + } + test("contramapValue with Id consistent with lmap"){ forAll { (c: Cokleisli[Id, Int, Long], f: Char => Int) => c.contramapValue[Char](f) should === (c.lmap(f)) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index e545030f2d..4c893160b3 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Arrow, Choice} +import cats.arrow.{CommutativeArrow, Choice} import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ @@ -27,8 +27,8 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", MonadReaderTests[Int => ?, Int].monadReader[Int, Int, Int]) checkAll("MonadReader[Int => ?, Int]", SerializableTests.serializable(MonadReader[Int => ?, Int])) - checkAll("Function1[Int, Int]", ArrowTests[Function1].arrow[Int, Int, Int, Int, Int, Int]) - checkAll("Arrow[Function1]", SerializableTests.serializable(Arrow[Function1])) + checkAll("Function1[Int, Int]", CommutativeArrowTests[Function1].commutativeArrow[Int, Int, Int, Int, Int, Int]) + checkAll("Arrow[Function1]", SerializableTests.serializable(CommutativeArrow[Function1])) checkAll("Function1[Int, Int]", ChoiceTests[Function1].choice[Int, Int, Int, Int]) checkAll("Choice[Function1]", SerializableTests.serializable(Choice[Function1])) diff --git a/tests/src/test/scala/cats/tests/IdTests.scala b/tests/src/test/scala/cats/tests/IdTests.scala index a55c6bfec0..a991b9a03a 100644 --- a/tests/src/test/scala/cats/tests/IdTests.scala +++ b/tests/src/test/scala/cats/tests/IdTests.scala @@ -9,8 +9,8 @@ class IdTests extends CatsSuite { checkAll("Id[Int]", BimonadTests[Id].bimonad[Int, Int, Int]) checkAll("Bimonad[Id]", SerializableTests.serializable(Bimonad[Id])) - checkAll("Id[Int]", MonadTests[Id].monad[Int, Int, Int]) - checkAll("Monad[Id]", SerializableTests.serializable(Monad[Id])) + checkAll("Id[Int]", CommutativeMonadTests[Id].commutativeMonad[Int, Int, Int]) + checkAll("CommutativeMonad[Id]", SerializableTests.serializable(CommutativeMonad[Id])) checkAll("Id[Int]", TraverseTests[Id].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Id]", SerializableTests.serializable(Traverse[Id])) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 4b11b361ce..9e33992889 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Arrow, Choice, Split, FunctionK} +import cats.arrow.{Arrow, Choice, CommutativeArrow, FunctionK} import cats.data.{EitherT, Kleisli, Reader} import cats.functor.{Contravariant, Strong} import cats.laws.discipline._ @@ -33,10 +33,19 @@ class KleisliTests extends CatsSuite { checkAll("Kleisli[Option, Int, Int]", CartesianTests[Kleisli[Option, Int, ?]].cartesian[Int, Int, Int]) checkAll("Cartesian[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Cartesian[Kleisli[Option, Int, ?]])) + checkAll("Kleisli[Option, Int, ?]", CommutativeMonadTests[Kleisli[Option, Int, ?]].commutativeMonad[Int, Int, Int]) + checkAll("CommutativeMonad[Kleisli[Option, Int, ?]]",SerializableTests.serializable(CommutativeMonad[Kleisli[Option, Int, ?]])) + + { + implicit val catsDataArrowForKleisli = Kleisli.catsDataArrowForKleisli[List] + checkAll("Kleisli[List, Int, Int]", ArrowTests[Kleisli[List, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) + checkAll("Arrow[Kleisli[List, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[List, ?, ?]])) + } + { - implicit val catsDataArrowForKleisli = Kleisli.catsDataArrowForKleisli[Option] - checkAll("Kleisli[Option, Int, Int]", ArrowTests[Kleisli[Option, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) - checkAll("Arrow[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[Option, ?, ?]])) + implicit val catsDataCommutativeArrowForKleisli = Kleisli.catsDataCommutativeArrowForKleisli[Option] + checkAll("Kleisli[Option, Int, Int]", CommutativeArrowTests[Kleisli[Option, ?, ?]].commutativeArrow[Int, Int, Int, Int, Int, Int]) + checkAll("CommutativeArrow[Kleisli[Option, ?, ?]]", SerializableTests.serializable(CommutativeArrow[Kleisli[Option, ?, ?]])) } { @@ -57,12 +66,6 @@ class KleisliTests extends CatsSuite { checkAll("MonadReader[Reader[?, ?], Int]", SerializableTests.serializable(MonadReader[Reader[Int, ?], Int])) } - { - implicit val kleisliSplit = Kleisli.catsDataSplitForKleisli[Option] - checkAll("Kleisli[Option, Int, Int]", SplitTests[Kleisli[Option, ?, ?]].split[Int, Int, Int, Int, Int, Int]) - checkAll("Split[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Split[Kleisli[Option, ?, ?]])) - } - { implicit val catsDataStrongForKleisli = Kleisli.catsDataStrongForKleisli[Option] checkAll("Kleisli[Option, Int, Int]", StrongTests[Kleisli[Option, ?, ?]].strong[Int, Int, Int, Int, Int, Int]) @@ -208,7 +211,6 @@ class KleisliTests extends CatsSuite { MonoidK[λ[α => Kleisli[List, α, α]]] Arrow[Kleisli[List, ?, ?]] Choice[Kleisli[List, ?, ?]] - Split[Kleisli[List, ?, ?]] Strong[Kleisli[List, ?, ?]] FlatMap[Kleisli[List, Int, ?]] Semigroup[Kleisli[List, Int, String]] @@ -223,8 +225,8 @@ class KleisliTests extends CatsSuite { Monoid[Kleisli[Id, Int, String]] MonoidK[λ[α => Kleisli[Id, α, α]]] Arrow[Kleisli[Id, ?, ?]] + CommutativeArrow[Kleisli[Id, ?, ?]] Choice[Kleisli[Id, ?, ?]] - Split[Kleisli[Id, ?, ?]] Strong[Kleisli[Id, ?, ?]] FlatMap[Kleisli[Id, Int, ?]] Semigroup[Kleisli[Id, Int, String]] @@ -239,8 +241,8 @@ class KleisliTests extends CatsSuite { Monoid[Reader[Int, String]] MonoidK[λ[α => Reader[α, α]]] Arrow[Reader[?, ?]] + CommutativeArrow[Reader[?, ?]] Choice[Reader[?, ?]] - Split[Reader[?, ?]] Strong[Reader[?, ?]] FlatMap[Reader[Int, ?]] Semigroup[Reader[Int, String]] diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index 8b00719d8c..3c4ccf9e6e 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -2,9 +2,10 @@ package cats package tests import cats.kernel.laws.{GroupLaws, OrderLaws} + import cats.data.{NonEmptyList, NonEmptyVector} -import cats.laws.discipline.{ComonadTests, MonadTests, NonEmptyTraverseTests, ReducibleTests, SemigroupKTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.{ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} class NonEmptyListTests extends CatsSuite { // Lots of collections here.. telling ScalaCheck to calm down a bit diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index c16367455f..bd86eba79f 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -14,8 +14,8 @@ class OptionTests extends CatsSuite { checkAll("Option[Int]", MonadCombineTests[Option].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Option]", SerializableTests.serializable(MonadCombine[Option])) - checkAll("Option[Int]", MonadTests[Option].monad[Int, Int, Int]) - checkAll("Monad[Option]", SerializableTests.serializable(Monad[Option])) + checkAll("Option[Int]", CommutativeMonadTests[Option].commutativeMonad[Int, Int, Int]) + checkAll("CommutativeMonad[Option]", SerializableTests.serializable(CommutativeMonad[Option])) checkAll("Option[Int] with Option", TraverseFilterTests[Option].traverseFilter[Int, Int, Int, Int, Option, Option]) checkAll("TraverseFilter[Option]", SerializableTests.serializable(TraverseFilter[Option])) diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index 726512e2a2..2ec46d9050 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -391,4 +391,7 @@ class WriterTTests extends CatsSuite { checkAll("WriterT[Option, ListWrapper[Int], ?]", MonadErrorTests[WriterT[Option, ListWrapper[Int], ?], Unit].monadError[Int, Int, Int]) checkAll("MonadError[WriterT[Option, ListWrapper[Int], ?], Unit]", SerializableTests.serializable(MonadError[WriterT[Option, ListWrapper[Int], ?], Unit])) } + + checkAll("WriterT[Option, Int, ?]", CommutativeMonadTests[WriterT[Option, Int, ?]].commutativeMonad[Int, Int, Int]) + checkAll("CommutativeMonad[WriterT[Option, Int, ?]]",SerializableTests.serializable(CommutativeMonad[WriterT[Option, Int, ?]])) }