From d14202f7b2cb8241a3e35ca5db2af58b70687c5a Mon Sep 17 00:00:00 2001 From: Diego Esteban Alonso Blas Date: Sun, 4 Jun 2017 14:52:25 +0100 Subject: [PATCH 1/7] 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 --- core/src/main/scala/cats/arrow/Arrow.scala | 31 +++++++++++++++++-- core/src/main/scala/cats/arrow/Split.scala | 24 -------------- core/src/main/scala/cats/data/Cokleisli.scala | 17 +++++----- core/src/main/scala/cats/data/Kleisli.scala | 12 +++---- 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 | 4 +-- laws/src/main/scala/cats/laws/SplitLaws.scala | 22 ------------- .../cats/laws/discipline/ArrowTests.scala | 3 +- .../cats/laws/discipline/SplitTests.scala | 31 ------------------- .../scala/cats/tests/CokleisliTests.scala | 5 +-- .../test/scala/cats/tests/KleisliTests.scala | 11 +------ 14 files changed, 52 insertions(+), 124 deletions(-) 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 delete mode 100644 laws/src/main/scala/cats/laws/SplitLaws.scala delete mode 100644 laws/src/main/scala/cats/laws/discipline/SplitTests.scala 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/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..676182d1bc 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, Compose} import cats.functor.{Contravariant, Profunctor} import cats.{CoflatMap, Comonad, Functor, Monad} import scala.annotation.tailrec @@ -74,8 +74,8 @@ private[data] sealed abstract class CokleisliInstances extends CokleisliInstance } 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 } + 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 +89,7 @@ private[data] sealed abstract class CokleisliInstances0 { } } -private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliSplit[F] with CokleisliProfunctor[F] { +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] = @@ -107,18 +107,15 @@ private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with Coklei override def dimap[A, B, C, D](fab: Cokleisli[F, A, B])(f: C => A)(g: B => D): Cokleisli[F, C, D] = 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) + override def split[A1, B1, A2, B2](f: Cokleisli[F, A1, B1], g: Cokleisli[F, A2, B2]): Cokleisli[F, (A1, A2), (B1, B2)] = + 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..fe12d113b8 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, Compose, FunctionK} import cats.functor.{Contravariant, Strong} /** @@ -132,8 +132,8 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 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 } @@ -163,15 +163,11 @@ private[data] sealed abstract class KleisliInstances5 { 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 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/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 0cf3dad050..7d220a7726 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 7c7ab4285e..b9fe51c345 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 Show.ToShowOps - 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..20b55ec9f1 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]] = 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/ArrowTests.scala b/laws/src/main/scala/cats/laws/discipline/ArrowTests.scala index 0a133a106a..0db8a07814 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( 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 9d25b2c4ab..c73d41dc2d 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 import cats.data.{Cokleisli, NonEmptyList} import cats.functor.{Contravariant, Profunctor} import cats.laws.discipline._ @@ -28,9 +28,6 @@ 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]])) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 50ea3673c3..444fa53ada 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, FunctionK} import cats.data.{EitherT, Kleisli, Reader} import cats.functor.{Contravariant, Strong} import cats.laws.discipline._ @@ -57,12 +57,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]) @@ -178,7 +172,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]] @@ -194,7 +187,6 @@ class KleisliTests extends CatsSuite { MonoidK[λ[α => Kleisli[Id, α, α]]] Arrow[Kleisli[Id, ?, ?]] Choice[Kleisli[Id, ?, ?]] - Split[Kleisli[Id, ?, ?]] Strong[Kleisli[Id, ?, ?]] FlatMap[Kleisli[Id, Int, ?]] Semigroup[Kleisli[Id, Int, String]] @@ -210,7 +202,6 @@ class KleisliTests extends CatsSuite { MonoidK[λ[α => Reader[α, α]]] Arrow[Reader[?, ?]] Choice[Reader[?, ?]] - Split[Reader[?, ?]] Strong[Reader[?, ?]] FlatMap[Reader[Int, ?]] Semigroup[Reader[Int, String]] From e59a0e0d5f04d620145f8cbbe98f13a42a9b7286 Mon Sep 17 00:00:00 2001 From: Diego Esteban Alonso Blas Date: Sun, 4 Jun 2017 19:00:16 +0100 Subject: [PATCH 2/7] 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). --- .../scala/cats/arrow/CommutativeArrow.scala | 13 ++++++ .../cats/laws/CommutativeArrowLaws.scala | 22 +++++++++ .../discipline/CommutativeArrowTests.scala | 46 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 core/src/main/scala/cats/arrow/CommutativeArrow.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeArrowLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeArrowTests.scala 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/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/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] } +} From 5380fa3a29a507bfef3eaa2ed32dc412fffb8297 Mon Sep 17 00:00:00 2001 From: Diego Esteban Alonso Blas Date: Sun, 4 Jun 2017 20:10:15 +0100 Subject: [PATCH 3/7] 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. --- .../main/scala/cats/CommutativeMonad.scala | 14 ++++++ .../main/scala/cats/instances/option.scala | 4 +- core/src/main/scala/cats/package.scala | 4 +- .../cats/laws/CommutativeMonadLaws.scala | 19 ++++++++ .../discipline/CommutativeMonadTests.scala | 45 +++++++++++++++++++ tests/src/test/scala/cats/tests/IdTests.scala | 4 +- .../test/scala/cats/tests/OptionTests.scala | 4 +- 7 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 core/src/main/scala/cats/CommutativeMonad.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala diff --git a/core/src/main/scala/cats/CommutativeMonad.scala b/core/src/main/scala/cats/CommutativeMonad.scala new file mode 100644 index 0000000000..d4d05b8fd8 --- /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] 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..a163fd1aca 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 NonEmptyTraverse[Id] = + new Bimonad[Id] with CommutativeMonad[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/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala new file mode 100644 index 0000000000..55331b4a2a --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala @@ -0,0 +1,19 @@ +package cats +package laws + +/** + * Laws that must be obeyed by any `CommutativeMonad`. + */ +trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] { + implicit override def F: CommutativeMonad[F] + + def monadCommutative[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 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/discipline/CommutativeMonadTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala new file mode 100644 index 0000000000..3f9ba16f3f --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.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 CommutativeMonadTests[F[_]] extends MonadTests[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]) + def props: Seq[(String, Prop)] = Seq( + "monad commutativity" -> forAll(laws.monadCommutative[A, B, C] _) + ) + } + } + +} + +object CommutativeMonadTests { + def apply[F[_]: CommutativeMonad]: CommutativeMonadTests[F] = + new CommutativeMonadTests[F] { + def laws: CommutativeMonadLaws[F] = CommutativeMonadLaws[F] + } +} 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/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])) From 0fd9a0fb2ad7cc3e3a6ef859f439ce2b14a7cb70 Mon Sep 17 00:00:00 2001 From: Diego Esteban Alonso Blas Date: Sun, 4 Jun 2017 23:51:10 +0100 Subject: [PATCH 4/7] Cats-1567 Kleisli Instances We introduce some instances of CommutativeArrow for Kleisli, which are based on CommutativeMonad. --- core/src/main/scala/cats/data/Kleisli.scala | 17 +++++++--- .../test/scala/cats/tests/KleisliTests.scala | 31 ++++++++++++++++--- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index fe12d113b8..7c296f8b14 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, FunctionK} +import cats.arrow.{Arrow, Category, Choice, CommutativeArrow, Compose, FunctionK} import cats.functor.{Contravariant, Strong} /** @@ -91,11 +91,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] @@ -116,6 +116,9 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { } private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { + 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 } } @@ -163,6 +166,10 @@ private[data] sealed abstract class KleisliInstances5 { new KleisliFunctor[F, A] { def F: Functor[F] = F0 } } +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] diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 444fa53ada..6b32f49001 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, FunctionK} +import cats.arrow.{Arrow, Choice, CommutativeArrow, FunctionK} import cats.data.{EitherT, Kleisli, Reader} import cats.functor.{Contravariant, Strong} import cats.laws.discipline._ @@ -34,9 +34,15 @@ class KleisliTests extends CatsSuite { checkAll("Cartesian[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Cartesian[Kleisli[Option, Int, ?]])) { - 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 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 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, ?, ?]])) } { @@ -185,6 +191,21 @@ class KleisliTests extends CatsSuite { MonadReader[Kleisli[Id, Int, ?], Int] Monoid[Kleisli[Id, Int, String]] MonoidK[λ[α => Kleisli[Id, α, α]]] + CommutativeArrow[Kleisli[Id, ?, ?]] + Choice[Kleisli[Id, ?, ?]] + Strong[Kleisli[Id, ?, ?]] + FlatMap[Kleisli[Id, Int, ?]] + Semigroup[Kleisli[Id, Int, String]] + SemigroupK[λ[α => Kleisli[Id, α, α]]] + + //F is List: not commutative + Functor[Kleisli[Id, Int, ?]] + Apply[Kleisli[Id, Int, ?]] + Applicative[Kleisli[Id, Int, ?]] + Monad[Kleisli[Id, Int, ?]] + MonadReader[Kleisli[Id, Int, ?], Int] + Monoid[Kleisli[Id, Int, String]] + MonoidK[λ[α => Kleisli[Id, α, α]]] Arrow[Kleisli[Id, ?, ?]] Choice[Kleisli[Id, ?, ?]] Strong[Kleisli[Id, ?, ?]] @@ -200,7 +221,7 @@ class KleisliTests extends CatsSuite { MonadReader[Reader[Int, ?], Int] Monoid[Reader[Int, String]] MonoidK[λ[α => Reader[α, α]]] - Arrow[Reader[?, ?]] + CommutativeArrow[Reader[?, ?]] Choice[Reader[?, ?]] Strong[Reader[?, ?]] FlatMap[Reader[Int, ?]] From 670955a2b0cc21752a1806ba05afc892553cd84b Mon Sep 17 00:00:00 2001 From: Diego Esteban Alonso Blas Date: Sun, 11 Jun 2017 23:35:52 +0100 Subject: [PATCH 5/7] Cats-1567 Split CommutativeMonad into Commutative FlatMap We introduce a new type class, CommutativeFlatMap, to load the commutativity law one level up. --- .../main/scala/cats/CommutativeFlatMap.scala | 14 ++++++ .../main/scala/cats/CommutativeMonad.scala | 2 +- .../cats/laws/CommutativeFlatMapLaws.scala | 19 ++++++++ .../cats/laws/CommutativeMonadLaws.scala | 7 +-- .../discipline/CommutativeFlatMapTests.scala | 45 +++++++++++++++++++ .../discipline/CommutativeMonadTests.scala | 9 ++-- 6 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 core/src/main/scala/cats/CommutativeFlatMap.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.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 index d4d05b8fd8..369541142a 100644 --- a/core/src/main/scala/cats/CommutativeMonad.scala +++ b/core/src/main/scala/cats/CommutativeMonad.scala @@ -11,4 +11,4 @@ import simulacrum.typeclass * * Must obey the laws defined in cats.laws.CommutativeMonadLaws. */ -@typeclass trait CommutativeMonad[F[_]] extends Monad[F] +@typeclass trait CommutativeMonad[F[_]] extends Monad[F] with CommutativeFlatMap[F] 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 index 55331b4a2a..1172b30165 100644 --- a/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala +++ b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala @@ -4,13 +4,8 @@ package laws /** * Laws that must be obeyed by any `CommutativeMonad`. */ -trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] { +trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] with CommutativeFlatMapLaws[F] { implicit override def F: CommutativeMonad[F] - - def monadCommutative[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 CommutativeMonadLaws { 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 index 3f9ba16f3f..c2a0975759 100644 --- a/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala @@ -4,9 +4,8 @@ package discipline import cats.laws.discipline.CartesianTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} -import Prop._ -trait CommutativeMonadTests[F[_]] extends MonadTests[F] { +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 @@ -28,10 +27,8 @@ trait CommutativeMonadTests[F[_]] extends MonadTests[F] { new RuleSet { def name: String = "commutative monad" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(monad[A, B, C]) - def props: Seq[(String, Prop)] = Seq( - "monad commutativity" -> forAll(laws.monadCommutative[A, B, C] _) - ) + def parents: Seq[RuleSet] = Seq(monad[A, B, C], commutativeFlatMap[A, B, C]) + def props: Seq[(String, Prop)] = Nil } } From 56efbec1c5eaff01094dcd035fda1c0b31b914dd Mon Sep 17 00:00:00 2001 From: Diego Esteban Alonso Blas Date: Mon, 12 Jun 2017 00:50:01 +0100 Subject: [PATCH 6/7] 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. --- .../scala/cats/CommutativeCoflatMap.scala | 14 ++++ .../main/scala/cats/CommutativeComonad.scala | 14 ++++ core/src/main/scala/cats/data/Cokleisli.scala | 66 +++++++++++-------- core/src/main/scala/cats/package.scala | 4 +- .../cats/laws/CommutativeCoflatMapLaws.scala | 18 +++++ .../cats/laws/CommutativeComonadLaws.scala | 14 ++++ .../CommutativeCoflatMapTests.scala | 49 ++++++++++++++ .../discipline/CommutativeComonadTests.scala | 45 +++++++++++++ .../scala/cats/tests/CokleisliTests.scala | 3 + 9 files changed, 199 insertions(+), 28 deletions(-) create mode 100644 core/src/main/scala/cats/CommutativeCoflatMap.scala create mode 100644 core/src/main/scala/cats/CommutativeComonad.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeCoflatMapLaws.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeComonadLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeCoflatMapTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeComonadTests.scala diff --git a/core/src/main/scala/cats/CommutativeCoflatMap.scala b/core/src/main/scala/cats/CommutativeCoflatMap.scala new file mode 100644 index 0000000000..6ce431a609 --- /dev/null +++ b/core/src/main/scala/cats/CommutativeCoflatMap.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative CoflatMap. + * + * Further than a CoflatMap, which just allows composition of dependent effectful functions, + * in a Commutative CoflatMap those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeCoflatMapLaws. + */ +@typeclass trait CommutativeCoflatMap[F[_]] extends CoflatMap[F] diff --git a/core/src/main/scala/cats/CommutativeComonad.scala b/core/src/main/scala/cats/CommutativeComonad.scala new file mode 100644 index 0000000000..8689c86e5b --- /dev/null +++ b/core/src/main/scala/cats/CommutativeComonad.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative Comonad. + * + * Further than a Comonad, which just allows composition of dependent effectful functions, + * in a Commutative Comonad those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeComonadLaws. + */ +@typeclass trait CommutativeComonad[F[_]] extends Comonad[F] with CommutativeCoflatMap[F] diff --git a/core/src/main/scala/cats/data/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index 676182d1bc..c86ab1cf2c 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -1,9 +1,9 @@ package cats package data -import cats.arrow.{Arrow, Category, Compose} +import cats.arrow.{Arrow, Category, CommutativeArrow, Compose} import cats.functor.{Contravariant, Profunctor} -import cats.{CoflatMap, Comonad, Functor, Monad} +import cats.{CoflatMap, Comonad, CommutativeComonad, Functor, Monad} import scala.annotation.tailrec /** @@ -45,35 +45,22 @@ 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) + implicit def catsDataCommutativeArrowForCokleisli[F[_]](implicit ev: CommutativeComonad[F]): CommutativeArrow[Cokleisli[F, ?, ?]] = + new CokleisliCommutativeArrow[F] { def F: CommutativeComonad[F] = ev } - 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 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 { +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 } @@ -89,6 +76,33 @@ private[data] sealed abstract class CokleisliInstances0 { } } +private[data] trait CokleisliCommutativeArrow[F[_]] extends CommutativeArrow[Cokleisli[F, ?, ?]] with CokleisliArrow[F] { + implicit def F: CommutativeComonad[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] @@ -107,7 +121,7 @@ private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with Coklei override def dimap[A, B, C, D](fab: Cokleisli[F, A, B])(f: C => A)(g: B => D): Cokleisli[F, C, D] = super[CokleisliProfunctor].dimap(fab)(f)(g) - override def split[A1, B1, A2, B2](f: Cokleisli[F, A1, B1], g: Cokleisli[F, A2, B2]): Cokleisli[F, (A1, A2), (B1, B2)] = + override 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))) } diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index a163fd1aca..b7646a5e97 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 CommutativeMonad[Id] with NonEmptyTraverse[Id] = - new Bimonad[Id] with CommutativeMonad[Id] with NonEmptyTraverse[Id] { + implicit val catsInstancesForId: Bimonad[Id] with CommutativeMonad[Id] with CommutativeComonad[Id] with NonEmptyTraverse[Id] = + new Bimonad[Id] with CommutativeMonad[Id] with CommutativeComonad[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/laws/src/main/scala/cats/laws/CommutativeCoflatMapLaws.scala b/laws/src/main/scala/cats/laws/CommutativeCoflatMapLaws.scala new file mode 100644 index 0000000000..af0c339158 --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeCoflatMapLaws.scala @@ -0,0 +1,18 @@ +package cats +package laws + +/** + * Laws that must be obeyed by any `CommutativeCoflatMap`. + */ +trait CommutativeCoflatMapLaws[F[_]] extends CoflatMapLaws[F] { + implicit override def F: CommutativeCoflatMap[F] + + def coflatmapCommutative[A, B, C](fa: F[A], fb: F[B], g: (F[A], F[B]) => C): IsEq[F[F[C]]] = + F.coflatMap(fa)(x => F.coflatMap(fb)(y => g(x, y))) <-> + F.coflatMap(fb)(y => F.coflatMap(fa)(x => g(x, y))) +} + +object CommutativeCoflatMapLaws { + def apply[F[_]](implicit ev: CommutativeCoflatMap[F]): CommutativeCoflatMapLaws[F] = + new CommutativeCoflatMapLaws[F] { def F: CommutativeCoflatMap[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/CommutativeComonadLaws.scala b/laws/src/main/scala/cats/laws/CommutativeComonadLaws.scala new file mode 100644 index 0000000000..c437fccd6d --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeComonadLaws.scala @@ -0,0 +1,14 @@ +package cats +package laws + +/** + * Laws that must be obeyed by any `CommutativeComonad`. + */ +trait CommutativeComonadLaws[F[_]] extends ComonadLaws[F] with CommutativeCoflatMapLaws[F] { + implicit override def F: CommutativeComonad[F] +} + +object CommutativeComonadLaws { + def apply[F[_]](implicit ev: CommutativeComonad[F]): CommutativeComonadLaws[F] = + new CommutativeComonadLaws[F] { def F: CommutativeComonad[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeCoflatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeCoflatMapTests.scala new file mode 100644 index 0000000000..1619dd58f8 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeCoflatMapTests.scala @@ -0,0 +1,49 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait CommutativeCoflatMapTests[F[_]] extends CoflatMapTests[F] { + def laws: CommutativeCoflatMapLaws[F] + + def commutativeCoflatmap[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + ArbFC: Arbitrary[F[C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + CogenFA: Cogen[F[A]], + CogenFB: Cogen[F[B]], + EqFABC: Eq[F[(A, B, C)]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFA: Eq[F[A]], + EqFFA: Eq[F[F[A]]], + EqFFFA: Eq[F[F[F[A]]]], + EqFFC: Eq[F[F[C]]], + EqFFFC: Eq[F[F[F[C]]]], + EqFInt: Eq[F[Int]] + ): RuleSet = { + new RuleSet { + def name: String = "commutative coflatmap" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(coflatMap[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "coflatmap commutativity" -> forAll(laws.coflatmapCommutative[A, B, C] _) + ) + } + } + +} + +object CommutativeCoflatMapTests { + def apply[F[_]: CommutativeCoflatMap]: CommutativeCoflatMapTests[F] = + new CommutativeCoflatMapTests[F] { + def laws: CommutativeCoflatMapLaws[F] = CommutativeCoflatMapLaws[F] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeComonadTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeComonadTests.scala new file mode 100644 index 0000000000..f717e4ec00 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeComonadTests.scala @@ -0,0 +1,45 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Cogen, Prop} + +trait CommutativeComonadTests[F[_]] extends ComonadTests[F] with CommutativeCoflatMapTests[F] { + def laws: CommutativeComonadLaws[F] + + def commutativeComonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + ArbFC: Arbitrary[F[C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + CogenFA: Cogen[F[A]], + CogenFB: Cogen[F[B]], + EqFABC: Eq[F[(A, B, C)]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFA: Eq[F[A]], + EqFFA: Eq[F[F[A]]], + EqFFFA: Eq[F[F[F[A]]]], + EqFFC: Eq[F[F[C]]], + EqFFFC: Eq[F[F[F[C]]]], + EqFInt: Eq[F[Int]] + ): RuleSet = { + new RuleSet { + def name: String = "commutative comonad" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(comonad[A, B, C], commutativeCoflatmap[A, B, C] ) + def props: Seq[(String, Prop)] = Nil + } + } +} + +object CommutativeComonadTests { + def apply[F[_]: CommutativeComonad]: CommutativeComonadTests[F] = + new CommutativeComonadTests[F] { + def laws: CommutativeComonadLaws[F] = CommutativeComonadLaws[F] + } +} diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index c73d41dc2d..14f16694a0 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -28,6 +28,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[Id, Int, Int]", CommutativeArrowTests[Cokleisli[Id, ?, ?]].commutativeArrow[Int, Int, Int, Int, Int, Int]) + checkAll("CommutativeArrow[Cokleisli[Id, ?, ?]]", SerializableTests.serializable(CommutativeArrow[Cokleisli[Id, ?, ?]])) + checkAll("Cokleisli[Option, Int, Int]", ContravariantTests[Cokleisli[Option, ?, Int]].contravariant[Int, Int, Int]) checkAll("Contravariant[Cokleisli[Option, ?, Int]]", SerializableTests.serializable(Contravariant[Cokleisli[Option, ?, Int]])) From b8f0658d9f768e6492d04de2a4c77b6d7b1a8b47 Mon Sep 17 00:00:00 2001 From: Diego Esteban Alonso Blas Date: Thu, 29 Jun 2017 00:46:03 +0100 Subject: [PATCH 7/7] 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. --- core/src/main/scala/cats/data/Cokleisli.scala | 3 +++ core/src/main/scala/cats/data/NonEmptyList.scala | 4 ++-- tests/src/test/scala/cats/tests/CokleisliTests.scala | 9 +++------ tests/src/test/scala/cats/tests/NonEmptyListTests.scala | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/data/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index c86ab1cf2c..4da5f742a0 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -48,6 +48,9 @@ private[data] sealed abstract class CokleisliInstances extends CokleisliInstance implicit def catsDataCommutativeArrowForCokleisli[F[_]](implicit ev: CommutativeComonad[F]): CommutativeArrow[Cokleisli[F, ?, ?]] = new CokleisliCommutativeArrow[F] { def F: CommutativeComonad[F] = ev } + implicit val catsDataCommutativeArrowForCokleisliId: CommutativeArrow[Cokleisli[Id, ?, ?]] = + catsDataCommutativeArrowForCokleisli[Id] + implicit def catsDataMonadForCokleisli[F[_], A]: Monad[Cokleisli[F, A, ?]] = new CokleisliMonad[F, A] diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index dcba546be6..0d67803e65 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -367,8 +367,8 @@ object NonEmptyList extends NonEmptyListInstances { private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 { implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList] with Reducible[NonEmptyList] - with Comonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] with Monad[NonEmptyList] = - new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with Comonad[NonEmptyList] + with CommutativeComonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] with Monad[NonEmptyList] = + new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with CommutativeComonad[NonEmptyList] with Monad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] { def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] = diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index 14f16694a0..09f61de585 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 +import cats.arrow.{ CommutativeArrow } import cats.data.{Cokleisli, NonEmptyList} import cats.functor.{Contravariant, Profunctor} import cats.laws.discipline._ @@ -28,9 +28,6 @@ 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[Id, Int, Int]", CommutativeArrowTests[Cokleisli[Id, ?, ?]].commutativeArrow[Int, Int, Int, Int, Int, Int]) - checkAll("CommutativeArrow[Cokleisli[Id, ?, ?]]", SerializableTests.serializable(CommutativeArrow[Cokleisli[Id, ?, ?]])) - checkAll("Cokleisli[Option, Int, Int]", ContravariantTests[Cokleisli[Option, ?, Int]].contravariant[Int, Int, Int]) checkAll("Contravariant[Cokleisli[Option, ?, Int]]", SerializableTests.serializable(Contravariant[Cokleisli[Option, ?, Int]])) @@ -38,8 +35,8 @@ class CokleisliTests extends SlowCatsSuite { // Ceremony to help scalac to do the right thing, see also #267. type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B] - checkAll("Cokleisli[NonEmptyList, Int, Int]", ArrowTests[CokleisliNEL].arrow[Int, Int, Int, Int, Int, Int]) - checkAll("Arrow[Cokleisli[NonEmptyList, ?, ?]]", SerializableTests.serializable(Arrow[CokleisliNEL])) + checkAll("Cokleisli[NonEmptyList, Int, Int]", CommutativeArrowTests[CokleisliNEL].commutativeArrow[Int, Int, Int, Int, Int, Int]) + checkAll("CommutativeArrow[Cokleisli[NonEmptyList, ?, ?]]", SerializableTests.serializable(CommutativeArrow[CokleisliNEL])) } { diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index e2b360777e..7054c3cac0 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -4,7 +4,7 @@ package tests import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.data.NonEmptyList -import cats.laws.discipline.{ComonadTests, SemigroupKTests, MonadTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests} +import cats.laws.discipline.{CommutativeComonadTests, SemigroupKTests, MonadTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests } import cats.laws.discipline.arbitrary._ class NonEmptyListTests extends CatsSuite { @@ -29,8 +29,8 @@ class NonEmptyListTests extends CatsSuite { checkAll("NonEmptyList[Int]", GroupLaws[NonEmptyList[Int]].semigroup) checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[NonEmptyList[Int]])) - checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) - checkAll("Comonad[NonEmptyList]", SerializableTests.serializable(Comonad[NonEmptyList])) + checkAll("NonEmptyList[Int]", CommutativeComonadTests[NonEmptyList].commutativeComonad[Int, Int, Int]) + checkAll("CommutativeComonad[NonEmptyList]", SerializableTests.serializable(CommutativeComonad[NonEmptyList])) checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].eqv) checkAll("Eq[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Eq[NonEmptyList[ListWrapper[Int]]]))