From a8de85bc9d1af69951adb7cf60c20fd5d1a6dbc0 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Mon, 11 Dec 2017 22:18:41 -0800 Subject: [PATCH 1/7] ArrowChoice, laws, function instance --- .../main/scala/cats/arrow/ArrowChoice.scala | 44 ++++++++++++ .../main/scala/cats/instances/function.scala | 15 +++-- core/src/main/scala/cats/syntax/all.scala | 1 + .../main/scala/cats/syntax/arrowChoice.scala | 6 ++ core/src/main/scala/cats/syntax/package.scala | 1 + .../scala/cats/laws/ArrowChoiceLaws.scala | 43 ++++++++++++ .../laws/discipline/ArrowChoiceTests.scala | 67 +++++++++++++++++++ .../test/scala/cats/tests/FunctionSuite.scala | 5 +- 8 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 core/src/main/scala/cats/arrow/ArrowChoice.scala create mode 100644 core/src/main/scala/cats/syntax/arrowChoice.scala create mode 100644 laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala diff --git a/core/src/main/scala/cats/arrow/ArrowChoice.scala b/core/src/main/scala/cats/arrow/ArrowChoice.scala new file mode 100644 index 0000000000..26e14278a4 --- /dev/null +++ b/core/src/main/scala/cats/arrow/ArrowChoice.scala @@ -0,0 +1,44 @@ +package cats +package arrow + +import simulacrum.typeclass + +/** + * Must obey the laws defined in cats.laws.ArrowChoiceLaws. + */ +@typeclass trait ArrowChoice[F[_, _]] extends Arrow[F] with Choice[F] { self => + + /** + * ArrowChoice yields Arrows with choice, allowing distribution + * over coproducts. + * + * Given two `F`s (`f` and `g`), create a new `F` with + * domain the coproduct of the domains of `f` and `g`, + * and codomain the coproduct of the codomains of `f` and `g`. + * This is the sum notion to `split`'s product. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import cats.arrow.ArrowChoice + * scala> val toLong: Int => Long = _.toLong + * scala> val toDouble: Float => Double = _.toDouble + * scala> val f: Either[Int, Float] => Either[Long, Double] = toLong +++ toDouble + * scala> f(Left(3)) + * res0: Either[Long,Double] = Left(5) + * scala> f(Right(3)) + * res1: Either[Long,Double] = Right(5.0) + * }}} + */ + @simulacrum.op("+++", alias = true) + def choose[A, B, C, D](f: F[A, C])(g: F[B, D]): F[Either[A, B], Either[C, D]] + + def left[A, B, C](fab: F[A, B]): F[Either[A, C], Either[B, C]] = + choose(fab)(lift(identity [C])) + + def right[A, B, C](fab: F[A, B]): F[Either[C, A], Either[C, B]] = + choose(lift(identity [C]))(fab) + + override def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C] = + rmap(choose(f)(g))(_.fold(identity, identity)) +} diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 19bbd678e9..6ace3f62a0 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -2,7 +2,7 @@ package cats package instances import cats.Contravariant -import cats.arrow.{Category, Choice, CommutativeArrow} +import cats.arrow.{Category, ArrowChoice, CommutativeArrow} import annotation.tailrec @@ -68,12 +68,13 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 { } } - 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) - } + implicit val catsStdInstancesForFunction1: ArrowChoice[Function1] with CommutativeArrow[Function1] = + new ArrowChoice[Function1] with CommutativeArrow[Function1] { + def choose[A, B, C, D](f: A => C)(g: B => D): Either[A, B] => Either[C, D] = + _.fold( + Left.apply [C, D] _ compose f, + Right.apply [C, D] _ compose g + ) def lift[A, B](f: A => B): A => B = f diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index e2ae40e6c5..b28ed78280 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -7,6 +7,7 @@ trait AllSyntax with ApplicativeErrorSyntax with ApplySyntax with ArrowSyntax + with ArrowChoiceSyntax with BifunctorSyntax with BifoldableSyntax with BitraverseSyntax diff --git a/core/src/main/scala/cats/syntax/arrowChoice.scala b/core/src/main/scala/cats/syntax/arrowChoice.scala new file mode 100644 index 0000000000..16adb051e9 --- /dev/null +++ b/core/src/main/scala/cats/syntax/arrowChoice.scala @@ -0,0 +1,6 @@ +package cats +package syntax + +import cats.arrow.ArrowChoice + +trait ArrowChoiceSyntax extends ArrowChoice.ToArrowChoiceOps diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index dd1cceb060..cf53d2c2b1 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -7,6 +7,7 @@ package object syntax { object applicativeError extends ApplicativeErrorSyntax object apply extends ApplySyntax object arrow extends ArrowSyntax + object arrowChoice extends ArrowChoiceSyntax object bifunctor extends BifunctorSyntax object bifoldable extends BifoldableSyntax object bitraverse extends BitraverseSyntax diff --git a/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala new file mode 100644 index 0000000000..c0df35a675 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala @@ -0,0 +1,43 @@ +package cats +package laws + +import cats.arrow.ArrowChoice +import cats.syntax.compose._ + +/** + * Laws that must be obeyed by any `cats.arrow.ArrowChoice`. + */ +trait ArrowChoiceLaws[F[_, _]] extends ArrowLaws[F] with ChoiceLaws[F] { + implicit override def F: ArrowChoice[F] + implicit def Function: ArrowChoice[Function1] + + def leftLiftCommute[A, B, C](f: A => B): IsEq[F[Either[A, C], Either[B, C]]] = + F.left[A, B, C](F.lift[A, B](f)) <-> F.lift[Either[A, C], Either[B, C]](Function.left[A, B, C](f)) + + def rightLiftCommute[A, B, C](f: A => B): IsEq[F[Either[C, A], Either[C, B]]] = + F.right[A, B, C](F.lift[A, B](f)) <-> F.lift[Either[C, A], Either[C, B]](Function.right[A, B, C](f)) + + def chooseLiftCommute[A, B, C, D](f: A => C, g: B => D): IsEq[F[Either[A, B], Either[C, D]]] = + F.lift[Either[A, B], Either[C, D]](Function.choose(f)(g)) <-> F.choose[A, B, C, D](F.lift(f))(F.lift(g)) + + def choiceLiftCommute[A, B, C](f: A => C, g: B => C): IsEq[F[Either[A, B], C]] = + F.lift[Either[A, B], C](Function.choice[A, B, C](f, g)) <-> F.choice[A, B, C](F.lift[A, C](f), F.lift[B, C](g)) + + def leftComposeCommute[A, B, C, D](f: A => B, g: B => C): IsEq[F[Either[A, D], Either[C, D]]] = + F.left[A, C, D](F.lift(g compose f)) <-> + (F.lift[Either[B, D], Either[C, D]](Function.left(g)) <<< + F.lift[Either[A, D], Either[B, D]](Function.left(f))) + + def rightComposeCommute[A, B, C, D](f: A => B, g: B => C): IsEq[F[Either[D, A], Either[D, C]]] = + F.right(F.lift(g compose f)) <-> + (F.lift[Either[D, B], Either[D, C]](Function.right(g)) <<< + F.lift[Either[D, A], Either[D, B]](Function.right(f))) +} + +object ArrowChoiceLaws { + def apply[F[_, _]](implicit ev: ArrowChoice[F], f: ArrowChoice[Function1]): ArrowChoiceLaws[F] = + new ArrowChoiceLaws[F] { + def F: ArrowChoice[F] = ev + def Function: ArrowChoice[Function1] = f + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala new file mode 100644 index 0000000000..8243210db4 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala @@ -0,0 +1,67 @@ +package cats +package laws +package discipline + +import cats.arrow.ArrowChoice +import cats.instances.function._ +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { + def laws: ArrowChoiceLaws[F] + + def arrowChoice[A: Arbitrary, B: Arbitrary, C: Arbitrary, D: Arbitrary, E: Arbitrary, G: Arbitrary](implicit + ArbFAB: Arbitrary[F[A, B]], + ArbFBC: Arbitrary[F[B, C]], + ArbFAC: Arbitrary[F[A, 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)]], + EqFABC: Eq[F[A, (B, C)]], + EqFEAED: Eq[F[(E, A), (E, D)]], + EqFACDBCD: Eq[F[((A, C), D), (B, (C, D))]], + EqFEitherABD: Eq[F[Either[A,B], D]], + EqFEitherABC: Eq[F[Either[A,B], C]], + EqFEitherABCD: Eq[F[Either[A, B], Either[C, D]]], + LEqFEitherABC: Eq[F[Either[A, C], Either[B, C]]], + REqFEitherABC: Eq[F[Either[C, A], Either[C, B]]], + LEqFEitherABCD: Eq[F[Either[D, A], Either[D, C]]], + REqFEitherABCD: Eq[F[Either[A, D], Either[C, D]]] + ): RuleSet = + new RuleSet { + def name: String = "arrowChoice" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq( + arrow[A, B, C, D, E, G], + choice[A, B, C, D] + ) + def props: Seq[(String, Prop)] = Seq( + "left and lift commute" -> forAll(laws.leftLiftCommute [A, B, C] _), + "right and lift commute" -> forAll(laws.rightLiftCommute [A, B, C] _), + "choose and lift commute" -> forAll(laws.chooseLiftCommute [A, B, C, D] _), + "choice and lift commute" -> forAll(laws.choiceLiftCommute [A, B, C] _), + "left and compose commute" -> forAll(laws.leftComposeCommute [A, B, C, D] _), + "right and compose commute" -> forAll(laws.rightComposeCommute [A, B, C, D] _) + ) + } +} +object ArrowChoiceTests { + def apply[F[_, _]: ArrowChoice]: ArrowChoiceTests[F] = + new ArrowChoiceTests[F] { def laws: ArrowChoiceLaws[F] = ArrowChoiceLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/FunctionSuite.scala b/tests/src/test/scala/cats/tests/FunctionSuite.scala index 5858d86769..dc962166bd 100644 --- a/tests/src/test/scala/cats/tests/FunctionSuite.scala +++ b/tests/src/test/scala/cats/tests/FunctionSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Choice, CommutativeArrow} +import cats.arrow.{ArrowChoice, Choice, CommutativeArrow} import cats.kernel.laws.HashLaws import cats.kernel.laws.discipline.{ BandTests, @@ -48,6 +48,9 @@ class FunctionSuite extends CatsSuite { checkAll("Function1[Int, Int]", ChoiceTests[Function1].choice[Int, Int, Int, Int]) checkAll("Choice[Function1]", SerializableTests.serializable(Choice[Function1])) + checkAll("Function1[Int, Int]", ArrowChoiceTests[Function1].arrowChoice[Int, Int, Int, Int, Int]) + checkAll("ArrowChoice[Function1]", SerializableTests.serializable(ArrowChoice[Function1])) + checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) From f315767c68f2eff3e157ba0b73dbaccd0b1e4e2d Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Mon, 11 Dec 2017 23:35:51 -0800 Subject: [PATCH 2/7] Kleisli instances, mima exceptions, tests --- build.sbt | 7 ++++ .../main/scala/cats/arrow/ArrowChoice.scala | 9 +++--- core/src/main/scala/cats/data/Kleisli.scala | 32 ++++++++++++++----- .../main/scala/cats/instances/function.scala | 4 +-- .../laws/discipline/ArrowChoiceTests.scala | 16 +++++----- .../test/scala/cats/tests/FunctionSuite.scala | 2 +- .../test/scala/cats/tests/KleisliSuite.scala | 6 ++++ 7 files changed, 52 insertions(+), 24 deletions(-) diff --git a/build.sbt b/build.sbt index c16c1803c2..1ec128f317 100644 --- a/build.sbt +++ b/build.sbt @@ -292,6 +292,13 @@ def mimaSettings(moduleName: String) = Seq( exclude[ReversedMissingMethodProblem]("cats.data.CommonIRWSTConstructors.liftK"), exclude[ReversedMissingMethodProblem]("cats.data.KleisliFunctions.liftF"), exclude[ReversedMissingMethodProblem]("cats.data.KleisliFunctions.liftK"), + exclude[IncompatibleResultTypeProblem]("cats.implicits.catsStdInstancesForFunction1"), + exclude[IncompatibleResultTypeProblem]("cats.instances.package#all.catsStdInstancesForFunction1"), + exclude[IncompatibleResultTypeProblem]("cats.instances.Function1Instances.catsStdInstancesForFunction1"), + exclude[ReversedMissingMethodProblem]("cats.instances.Function1Instances.catsStdInstancesForFunction1"), + exclude[ReversedMissingMethodProblem]("cats.instances.Function1Instances.cats$instances$Function1Instances$_setter_$catsStdInstancesForFunction1_="), + exclude[IncompatibleResultTypeProblem]("cats.instances.package#function.catsStdInstancesForFunction1"), + exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice#ToArrowChoiceOps.toArrowChoiceOps"), exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftF"), exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftK"), exclude[ReversedMissingMethodProblem]("cats.NonEmptyParallel.parForEffect"), diff --git a/core/src/main/scala/cats/arrow/ArrowChoice.scala b/core/src/main/scala/cats/arrow/ArrowChoice.scala index 26e14278a4..c0e7870dbb 100644 --- a/core/src/main/scala/cats/arrow/ArrowChoice.scala +++ b/core/src/main/scala/cats/arrow/ArrowChoice.scala @@ -20,24 +20,23 @@ import simulacrum.typeclass * Example: * {{{ * scala> import cats.implicits._ - * scala> import cats.arrow.ArrowChoice * scala> val toLong: Int => Long = _.toLong * scala> val toDouble: Float => Double = _.toDouble * scala> val f: Either[Int, Float] => Either[Long, Double] = toLong +++ toDouble * scala> f(Left(3)) - * res0: Either[Long,Double] = Left(5) + * res0: Either[Long,Double] = Left(3) * scala> f(Right(3)) - * res1: Either[Long,Double] = Right(5.0) + * res1: Either[Long,Double] = Right(3.0) * }}} */ @simulacrum.op("+++", alias = true) def choose[A, B, C, D](f: F[A, C])(g: F[B, D]): F[Either[A, B], Either[C, D]] def left[A, B, C](fab: F[A, B]): F[Either[A, C], Either[B, C]] = - choose(fab)(lift(identity [C])) + choose(fab)(lift(identity[C])) def right[A, B, C](fab: F[A, B]): F[Either[C, A], Either[C, B]] = - choose(lift(identity [C]))(fab) + choose(lift(identity[C]))(fab) override def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C] = rmap(choose(f)(g))(_.fold(identity, identity)) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 5e86a0cf59..bcf23d4ec9 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -130,11 +130,10 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { implicit def F: Monad[F] = F0 } - implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] = - new KleisliArrow[F] { + implicit def catsDataArrowChoiceForKleisli[F[_]](implicit M: Monad[F]): ArrowChoice[Kleisli[F, ?, ?]] = + new KleisliArrowChoice[F] { def F: Monad[F] = M } - } private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { @@ -147,11 +146,10 @@ private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 implicit def catsDataMonadForKleisliId[A]: Monad[Kleisli[Id, A, ?]] = catsDataMonadForKleisli[Id, A] - implicit def catsDataCommutativeArrowForKleisli[F[_]](implicit M: CommutativeMonad[F]): CommutativeArrow[Kleisli[F, ?, ?]] = - new KleisliCommutativeArrow[F] {def F: CommutativeMonad[F] = M } - - implicit val catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] = - catsDataCommutativeArrowForKleisli[Id] + implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] = + new KleisliArrow[F] { + def F: Monad[F] = M + } implicit def catsDataContravariantMonoidalForKleisli[F[_], A](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[Kleisli[F, A, ?]] = new KleisliContravariantMonoidal[F, A] { def F: ContravariantMonoidal[F] = F0 } @@ -161,6 +159,12 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 implicit def catsDataMonadForKleisli[F[_], A](implicit M: Monad[F]): Monad[Kleisli[F, A, ?]] = new KleisliMonad[F, A] { 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 catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] = + catsDataCommutativeArrowForKleisli[Id] + implicit def catsDataParallelForKleisli[F[_], M[_], A] (implicit P: Parallel[M, F]): Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]] = new Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]]{ implicit val appF = P.applicative @@ -237,6 +241,18 @@ private[data] trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleis implicit def F: CommutativeMonad[F] } +private[data] trait KleisliArrowChoice[F[_]] extends ArrowChoice[Kleisli[F, ?, ?]] with KleisliArrow[F] { + implicit def F: Monad[F] + + def choose[A, B, C, D](f: Kleisli[F, A, C])(g: Kleisli[F, B, D]): Kleisli[F, Either[A, B], Either[C, D]] = + Kleisli( + (fe: Either[A, B]) => + fe match { + case Left(a) => F.map(f(a))(Left.apply _) + case Right(b) => F.map(g(b))(Right.apply _) + }) +} + private[data] trait KleisliArrow[F[_]] extends Arrow[Kleisli[F, ?, ?]] with KleisliCategory[F] with KleisliStrong[F] { implicit def F: Monad[F] diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 6ace3f62a0..1770e5f28a 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -72,8 +72,8 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 { new ArrowChoice[Function1] with CommutativeArrow[Function1] { def choose[A, B, C, D](f: A => C)(g: B => D): Either[A, B] => Either[C, D] = _.fold( - Left.apply [C, D] _ compose f, - Right.apply [C, D] _ compose g + Left.apply[C, D] _ compose f, + Right.apply[C, D] _ compose g ) def lift[A, B](f: A => B): A => B = f diff --git a/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala index 8243210db4..2a3bfc68f5 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala @@ -36,8 +36,8 @@ trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { EqFABC: Eq[F[A, (B, C)]], EqFEAED: Eq[F[(E, A), (E, D)]], EqFACDBCD: Eq[F[((A, C), D), (B, (C, D))]], - EqFEitherABD: Eq[F[Either[A,B], D]], - EqFEitherABC: Eq[F[Either[A,B], C]], + EqFEitherABD: Eq[F[Either[A, B], D]], + EqFEitherABC: Eq[F[Either[A, B], C]], EqFEitherABCD: Eq[F[Either[A, B], Either[C, D]]], LEqFEitherABC: Eq[F[Either[A, C], Either[B, C]]], REqFEitherABC: Eq[F[Either[C, A], Either[C, B]]], @@ -52,12 +52,12 @@ trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { choice[A, B, C, D] ) def props: Seq[(String, Prop)] = Seq( - "left and lift commute" -> forAll(laws.leftLiftCommute [A, B, C] _), - "right and lift commute" -> forAll(laws.rightLiftCommute [A, B, C] _), - "choose and lift commute" -> forAll(laws.chooseLiftCommute [A, B, C, D] _), - "choice and lift commute" -> forAll(laws.choiceLiftCommute [A, B, C] _), - "left and compose commute" -> forAll(laws.leftComposeCommute [A, B, C, D] _), - "right and compose commute" -> forAll(laws.rightComposeCommute [A, B, C, D] _) + "left and lift commute" -> forAll(laws.leftLiftCommute[A, B, C] _), + "right and lift commute" -> forAll(laws.rightLiftCommute[A, B, C] _), + "choose and lift commute" -> forAll(laws.chooseLiftCommute[A, B, C, D] _), + "choice and lift commute" -> forAll(laws.choiceLiftCommute[A, B, C] _), + "left and compose commute" -> forAll(laws.leftComposeCommute[A, B, C, D] _), + "right and compose commute" -> forAll(laws.rightComposeCommute[A, B, C, D] _) ) } } diff --git a/tests/src/test/scala/cats/tests/FunctionSuite.scala b/tests/src/test/scala/cats/tests/FunctionSuite.scala index dc962166bd..edd4aad499 100644 --- a/tests/src/test/scala/cats/tests/FunctionSuite.scala +++ b/tests/src/test/scala/cats/tests/FunctionSuite.scala @@ -48,7 +48,7 @@ class FunctionSuite extends CatsSuite { checkAll("Function1[Int, Int]", ChoiceTests[Function1].choice[Int, Int, Int, Int]) checkAll("Choice[Function1]", SerializableTests.serializable(Choice[Function1])) - checkAll("Function1[Int, Int]", ArrowChoiceTests[Function1].arrowChoice[Int, Int, Int, Int, Int]) + checkAll("Function1[Int, Int]", ArrowChoiceTests[Function1].arrowChoice[Int, Int, Int, Int, Int, Int]) checkAll("ArrowChoice[Function1]", SerializableTests.serializable(ArrowChoice[Function1])) checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/KleisliSuite.scala b/tests/src/test/scala/cats/tests/KleisliSuite.scala index d9d8074b8f..bd689029ca 100644 --- a/tests/src/test/scala/cats/tests/KleisliSuite.scala +++ b/tests/src/test/scala/cats/tests/KleisliSuite.scala @@ -45,6 +45,12 @@ class KleisliSuite extends CatsSuite { checkAll("Arrow[Kleisli[List, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[List, ?, ?]])) } + { + implicit val catsDataArrowChoiceForKleisli = Kleisli.catsDataArrowChoiceForKleisli[List] + checkAll("Kleisli[List, Int, Int]", ArrowChoiceTests[Kleisli[List, ?, ?]].arrowChoice[Int, Int, Int, Int, Int, Int]) + checkAll("ArrowChoice[Kleisli[List, ?, ?]]", SerializableTests.serializable(ArrowChoice[Kleisli[List, ?, ?]])) + } + { implicit val catsDataCommutativeArrowForKleisli = Kleisli.catsDataCommutativeArrowForKleisli[Option] checkAll("Kleisli[Option, Int, Int]", CommutativeArrowTests[Kleisli[Option, ?, ?]].commutativeArrow[Int, Int, Int, Int, Int, Int]) From 7369b6f485e0de5312e3449a833d31c840e3dc10 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Mon, 11 Dec 2017 23:58:59 -0800 Subject: [PATCH 3/7] Update symbols table --- docs/src/main/tut/faq.md | 55 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/src/main/tut/faq.md b/docs/src/main/tut/faq.md index b6d41a9e3c..c2d8b997b0 100644 --- a/docs/src/main/tut/faq.md +++ b/docs/src/main/tut/faq.md @@ -211,33 +211,34 @@ The `~>`, `⊥`, `⊤`, `:<:` and `:≺:` symbols can be imported with `import c All other symbols can be imported with `import cats.implicits._` -| Symbol | Name | Nickname | Type Class | Signature | -| -------------------------------- | -------------------------| ---------------- | ----------------------- | --------------------------------------------------------------------| -| `fa *> fb` | followed by | | `Apply[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` | -| `fa <* fb` | for effect | | `Apply[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` | -| `x === y` | equals | | `Eq[A]` | `eqv(x: A, y: A): Boolean` | -| `x =!= y` | not equals | | `Eq[A]` | `neqv(x: A, y: A): Boolean` | -| `fa >>= f` | flatMap | | `FlatMap[F[_]]` | `flatMap(fa: F[A])(f: A => F[B]): F[B]` | -| `fa >> fb` | followed by | | `FlatMap[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` | -| x |-| y | remove | | `Group[A]` | `remove(x: A, y: A): A` | -| `x > y` | greater than | | `PartialOrder[A]` | `gt(x: A, y: A): Boolean` | -| `x >= y` | greater than or equal | | `PartialOrder[A]` | `gteq(x: A, y: A): Boolean` | -| `x < y` | less than | | `PartialOrder[A]` | `lt(x: A, y: A): Boolean` | -| `x <= y` | less than or equal | | `PartialOrder[A]` | `lteq(x: A, y: A): Boolean` | -| x |+| y | Semigroup combine | | `Semigroup[A]` | `combine(x: A, y: A): A` | -| `x <+> y` | SemigroupK combine | | `SemigroupK[F[_]]` | `combineK(x: F[A], y: F[A]): F[A]` | -| `f <<< g` | Arrow compose | | `Compose[F[_, _]]` | `compose(f: F[B, C], g: F[A, B]): F[A, C]` | -| `f >>> g` | Arrow andThen | | `Compose[F[_, _]]` | `andThen(f: F[B, C], g: F[A, B]): F[A, C]` | -| `f &&& g` | Arrow merge | | `Arrow[F[_, _]]` | `merge[A, B, C](f: F[A, B], g: F[A, C]): F[A, (B, C)]` | -| `f -< g` | Arrow combine and bypass | | `Arrow[F[_, _]]` | `combineAndByPass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)]` | -| `F ~> G` | natural transformation | | `FunctionK[F[_], G[_]]` | `FunctionK` alias | -| `F :<: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias | -| `F :≺: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias | -| `fa &> fb` | parallel followed by | | `Parallel[M[_], F[_]]` | `parFollowedBy[A, B](ma: M[A])(mb: M[B]): M[B]` | -| `fa <& fb` | parallel for effect | | `Parallel[M[_], F[_]]` | `parForEffect[A, B](ma: M[A])(mb: M[B]): M[A]` | -| `⊥` | bottom | | N/A | `Nothing` | -| `⊤` | top | | N/A | `Any` | -| `fa << fb` (Deprecated) | for effect | | `FlatMap[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` | +| Symbol | Name | Nickname | Type Class | Signature | +| -------------------------------- | -------------------------| ---------------- | ----------------------- | -----------------------------------------------------------------------------| +| `fa *> fb` | followed by | | `Apply[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` | +| `fa <* fb` | for effect | | `Apply[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` | +| `x === y` | equals | | `Eq[A]` | `eqv(x: A, y: A): Boolean` | +| `x =!= y` | not equals | | `Eq[A]` | `neqv(x: A, y: A): Boolean` | +| `fa >>= f` | flatMap | | `FlatMap[F[_]]` | `flatMap(fa: F[A])(f: A => F[B]): F[B]` | +| `fa >> fb` | followed by | | `FlatMap[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` | +| x |-| y | remove | | `Group[A]` | `remove(x: A, y: A): A` | +| `x > y` | greater than | | `PartialOrder[A]` | `gt(x: A, y: A): Boolean` | +| `x >= y` | greater than or equal | | `PartialOrder[A]` | `gteq(x: A, y: A): Boolean` | +| `x < y` | less than | | `PartialOrder[A]` | `lt(x: A, y: A): Boolean` | +| `x <= y` | less than or equal | | `PartialOrder[A]` | `lteq(x: A, y: A): Boolean` | +| x |+| y | Semigroup combine | | `Semigroup[A]` | `combine(x: A, y: A): A` | +| `x <+> y` | SemigroupK combine | | `SemigroupK[F[_]]` | `combineK(x: F[A], y: F[A]): F[A]` | +| `f <<< g` | Arrow compose | | `Compose[F[_, _]]` | `compose(f: F[B, C], g: F[A, B]): F[A, C]` | +| `f >>> g` | Arrow andThen | | `Compose[F[_, _]]` | `andThen(f: F[B, C], g: F[A, B]): F[A, C]` | +| `f &&& g` | Arrow merge | | `Arrow[F[_, _]]` | `merge[A, B, C](f: F[A, B], g: F[A, C]): F[A, (B, C)]` | +| `f -< g` | Arrow combine and bypass | | `Arrow[F[_, _]]` | `combineAndByPass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)]` | +| `f +++ g` | ArrowChoice choose | | `ArrowChoice[F[_, _]]` | `choose[A, B, C, D](f: F[A, C])(g: F[B, D]): F[Either [A, B], Either[C, D]]` | +| `F ~> G` | natural transformation | | `FunctionK[F[_], G[_]]` | `FunctionK` alias | +| `F :<: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias | +| `F :≺: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias | +| `fa &> fb` | parallel followed by | | `Parallel[M[_], F[_]]` | `parFollowedBy[A, B](ma: M[A])(mb: M[B]): M[B]` | +| `fa <& fb` | parallel for effect | | `Parallel[M[_], F[_]]` | `parForEffect[A, B](ma: M[A])(mb: M[B]): M[A]` | +| `⊥` | bottom | | N/A | `Nothing` | +| `⊤` | top | | N/A | `Any` | +| `fa << fb` (Deprecated) | for effect | | `FlatMap[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` | ## How can I test instances against their type classes' laws? From 791d9d15c3b010af479e6a994331375482f8b4d8 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Tue, 12 Dec 2017 14:37:20 -0800 Subject: [PATCH 4/7] Consolidate instances, add ||| --- build.sbt | 4 +++ core/src/main/scala/cats/arrow/Choice.scala | 1 + core/src/main/scala/cats/data/Kleisli.scala | 27 +++++++------------ docs/src/main/tut/faq.md | 1 + .../test/scala/cats/tests/KleisliSuite.scala | 2 +- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index 1ec128f317..57e2300325 100644 --- a/build.sbt +++ b/build.sbt @@ -299,6 +299,10 @@ def mimaSettings(moduleName: String) = Seq( exclude[ReversedMissingMethodProblem]("cats.instances.Function1Instances.cats$instances$Function1Instances$_setter_$catsStdInstancesForFunction1_="), exclude[IncompatibleResultTypeProblem]("cats.instances.package#function.catsStdInstancesForFunction1"), exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice#ToArrowChoiceOps.toArrowChoiceOps"), + exclude[DirectMissingMethodProblem]("cats.data.KleisliInstances.catsDataArrowForKleisli"), + exclude[MissingClassProblem]("cats.data.KleisliArrow"), + exclude[MissingTypesProblem]("cats.data.KleisliCommutativeArrow"), + exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.choose"), exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftF"), exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftK"), exclude[ReversedMissingMethodProblem]("cats.NonEmptyParallel.parForEffect"), diff --git a/core/src/main/scala/cats/arrow/Choice.scala b/core/src/main/scala/cats/arrow/Choice.scala index 7c1b4ba008..9d94fc87fd 100644 --- a/core/src/main/scala/cats/arrow/Choice.scala +++ b/core/src/main/scala/cats/arrow/Choice.scala @@ -23,6 +23,7 @@ import simulacrum.typeclass * res0: String = false is a boolean * }}} */ + @simulacrum.op("|||", alias = true) def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C] /** diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index bcf23d4ec9..76a5950dd1 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -146,11 +146,6 @@ private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 implicit def catsDataMonadForKleisliId[A]: Monad[Kleisli[Id, A, ?]] = catsDataMonadForKleisli[Id, A] - implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] = - new KleisliArrow[F] { - def F: Monad[F] = M - } - implicit def catsDataContravariantMonoidalForKleisli[F[_], A](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[Kleisli[F, A, ?]] = new KleisliContravariantMonoidal[F, A] { def F: ContravariantMonoidal[F] = F0 } } @@ -159,7 +154,7 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 implicit def catsDataMonadForKleisli[F[_], A](implicit M: Monad[F]): Monad[Kleisli[F, A, ?]] = new KleisliMonad[F, A] { def F: Monad[F] = M } - implicit def catsDataCommutativeArrowForKleisli[F[_]](implicit M: CommutativeMonad[F]): CommutativeArrow[Kleisli[F, ?, ?]] = + implicit def catsDataCommutativeArrowForKleisli[F[_]](implicit M: CommutativeMonad[F]): CommutativeArrow[Kleisli[F, ?, ?]] with ArrowChoice[Kleisli[F, ?, ?]] = new KleisliCommutativeArrow[F] {def F: CommutativeMonad[F] = M } implicit val catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] = @@ -237,13 +232,19 @@ private[data] sealed abstract class KleisliInstances7 { new KleisliFunctor[F, A] { def F: Functor[F] = F0 } } -private[data] trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleisli[F, ?, ?]] with KleisliArrow[F] { +private[data] trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleisli[F, ?, ?]] with KleisliArrowChoice[F] { implicit def F: CommutativeMonad[F] } -private[data] trait KleisliArrowChoice[F[_]] extends ArrowChoice[Kleisli[F, ?, ?]] with KleisliArrow[F] { +private[data] trait KleisliArrowChoice[F[_]] extends ArrowChoice[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))) + + 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))) } + def choose[A, B, C, D](f: Kleisli[F, A, C])(g: Kleisli[F, B, D]): Kleisli[F, Either[A, B], Either[C, D]] = Kleisli( (fe: Either[A, B]) => @@ -253,16 +254,6 @@ private[data] trait KleisliArrowChoice[F[_]] extends ArrowChoice[Kleisli[F, ?, ? }) } -private[data] 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))) - - 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))) } -} - private[data] trait KleisliStrong[F[_]] extends Strong[Kleisli[F, ?, ?]] { implicit def F: Functor[F] diff --git a/docs/src/main/tut/faq.md b/docs/src/main/tut/faq.md index c2d8b997b0..ee009703f8 100644 --- a/docs/src/main/tut/faq.md +++ b/docs/src/main/tut/faq.md @@ -231,6 +231,7 @@ All other symbols can be imported with `import cats.implicits._` | `f &&& g` | Arrow merge | | `Arrow[F[_, _]]` | `merge[A, B, C](f: F[A, B], g: F[A, C]): F[A, (B, C)]` | | `f -< g` | Arrow combine and bypass | | `Arrow[F[_, _]]` | `combineAndByPass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)]` | | `f +++ g` | ArrowChoice choose | | `ArrowChoice[F[_, _]]` | `choose[A, B, C, D](f: F[A, C])(g: F[B, D]): F[Either [A, B], Either[C, D]]` | +| `f ||| g` | Choice choice | | `Choice[F[_, _]]` | `choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C]` | | `F ~> G` | natural transformation | | `FunctionK[F[_], G[_]]` | `FunctionK` alias | | `F :<: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias | | `F :≺: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias | diff --git a/tests/src/test/scala/cats/tests/KleisliSuite.scala b/tests/src/test/scala/cats/tests/KleisliSuite.scala index bd689029ca..f0b0c54178 100644 --- a/tests/src/test/scala/cats/tests/KleisliSuite.scala +++ b/tests/src/test/scala/cats/tests/KleisliSuite.scala @@ -40,7 +40,7 @@ class KleisliSuite extends CatsSuite { checkAll("CommutativeMonad[Kleisli[Option, Int, ?]]",SerializableTests.serializable(CommutativeMonad[Kleisli[Option, Int, ?]])) { - implicit val catsDataArrowForKleisli = Kleisli.catsDataArrowForKleisli[List] + implicit val catsDataArrowForKleisli = Kleisli.catsDataArrowChoiceForKleisli[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, ?, ?]])) } From 93574b38ee20d9b33e245b16c1afc06a1133b7f0 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Tue, 12 Dec 2017 20:04:10 -0800 Subject: [PATCH 5/7] Fix laws --- build.sbt | 7 ++++ .../scala/cats/laws/ArrowChoiceLaws.scala | 32 ++++++++++--------- .../laws/discipline/ArrowChoiceTests.scala | 17 +++++----- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/build.sbt b/build.sbt index 57e2300325..408078098e 100644 --- a/build.sbt +++ b/build.sbt @@ -303,6 +303,13 @@ def mimaSettings(moduleName: String) = Seq( exclude[MissingClassProblem]("cats.data.KleisliArrow"), exclude[MissingTypesProblem]("cats.data.KleisliCommutativeArrow"), exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.choose"), + exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.choice"), + exclude[InheritedNewAbstractMethodProblem]("cats.arrow.Choice.codiagonal"), + exclude[InheritedNewAbstractMethodProblem]("cats.arrow.Choice.choice"), + exclude[InheritedNewAbstractMethodProblem]("cats.data.KleisliArrowChoice.choose"), + exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.left"), + exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.right"), + exclude[ReversedMissingMethodProblem]("cats.arrow.Choice#Ops.|||"), exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftF"), exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftK"), exclude[ReversedMissingMethodProblem]("cats.NonEmptyParallel.parForEffect"), diff --git a/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala index c0df35a675..10844dae80 100644 --- a/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala +++ b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala @@ -2,6 +2,7 @@ package cats package laws import cats.arrow.ArrowChoice +import cats.syntax.arrowChoice._ import cats.syntax.compose._ /** @@ -11,27 +12,28 @@ trait ArrowChoiceLaws[F[_, _]] extends ArrowLaws[F] with ChoiceLaws[F] { implicit override def F: ArrowChoice[F] implicit def Function: ArrowChoice[Function1] + def sumAssoc[A, B, C](e: Either[Either[A, B], C]): Either[A, Either[B, C]] = + e match { + case Left(Left(x)) => Left(x) + case Left(Right(y)) => Right(Left(y)) + case Right(z) => Right(Right(z)) + } + def leftLiftCommute[A, B, C](f: A => B): IsEq[F[Either[A, C], Either[B, C]]] = F.left[A, B, C](F.lift[A, B](f)) <-> F.lift[Either[A, C], Either[B, C]](Function.left[A, B, C](f)) - def rightLiftCommute[A, B, C](f: A => B): IsEq[F[Either[C, A], Either[C, B]]] = - F.right[A, B, C](F.lift[A, B](f)) <-> F.lift[Either[C, A], Either[C, B]](Function.right[A, B, C](f)) - - def chooseLiftCommute[A, B, C, D](f: A => C, g: B => D): IsEq[F[Either[A, B], Either[C, D]]] = - F.lift[Either[A, B], Either[C, D]](Function.choose(f)(g)) <-> F.choose[A, B, C, D](F.lift(f))(F.lift(g)) + def leftComposeCommute[A, B, C, D](f: F[A, B], g: F[B, C]): IsEq[F[Either[A, D], Either[C, D]]] = + F.left(f >>> g) <-> (F.left(f) >>> F.left[B, C, D](g)) - def choiceLiftCommute[A, B, C](f: A => C, g: B => C): IsEq[F[Either[A, B], C]] = - F.lift[Either[A, B], C](Function.choice[A, B, C](f, g)) <-> F.choice[A, B, C](F.lift[A, C](f), F.lift[B, C](g)) + def leftAndThenLiftedLeftApplyCommutes[A, B, C](f: F[A, B]): IsEq[F[A, Either[B, C]]] = + (f >>> F.lift[B, Either[B, C]](Left.apply[B, C])) <-> (F.lift[A, Either[A, C]](Left.apply[A, C] _) >>> F.left(f)) - def leftComposeCommute[A, B, C, D](f: A => B, g: B => C): IsEq[F[Either[A, D], Either[C, D]]] = - F.left[A, C, D](F.lift(g compose f)) <-> - (F.lift[Either[B, D], Either[C, D]](Function.left(g)) <<< - F.lift[Either[A, D], Either[B, D]](Function.left(f))) + def leftAndThenRightIdentityCommutes[A, B, C, D](f: F[A, B], g: C => D): IsEq[F[Either[A, C], Either[B, D]]] = + (F.left(f) >>> F.lift(identity[B] _ +++ g)) <-> (F.lift(identity[A] _ +++ g) >>> F.left(f)) - def rightComposeCommute[A, B, C, D](f: A => B, g: B => C): IsEq[F[Either[D, A], Either[D, C]]] = - F.right(F.lift(g compose f)) <-> - (F.lift[Either[D, B], Either[D, C]](Function.right(g)) <<< - F.lift[Either[D, A], Either[D, B]](Function.right(f))) + def leftTwiceCommutesWithSumAssociation[A, B, C, D](f: F[A, D]): IsEq[F[Either[Either[A, B], C], Either[D, Either[B, C]]]] = + (F.left[Either[A, B], Either[D, B], C](F.left[A, D, B](f)) >>> F.lift[Either[Either[D, B], C], Either[D, Either[B, C]]](sumAssoc[D, B, C])) <-> + (F.lift[Either[Either[A, B], C], Either[A, Either[B, C]]](sumAssoc[A, B, C]) >>> F.left(f)) } object ArrowChoiceLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala index 2a3bfc68f5..8628d756fd 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala @@ -14,6 +14,7 @@ trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { ArbFAB: Arbitrary[F[A, B]], ArbFBC: Arbitrary[F[B, C]], ArbFAC: Arbitrary[F[A, C]], + ArbFAD: Arbitrary[F[A, D]], ArbFCD: Arbitrary[F[C, D]], ArbFDE: Arbitrary[F[D, E]], ArbFEG: Arbitrary[F[E, G]], @@ -37,12 +38,11 @@ trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { EqFEAED: Eq[F[(E, A), (E, D)]], EqFACDBCD: Eq[F[((A, C), D), (B, (C, D))]], EqFEitherABD: Eq[F[Either[A, B], D]], - EqFEitherABC: Eq[F[Either[A, B], C]], - EqFEitherABCD: Eq[F[Either[A, B], Either[C, D]]], + EqFEitherCoABC: Eq[F[A, Either[B, C]]], + REqFEitherACD: Eq[F[Either[A, D], Either[C, D]]], LEqFEitherABC: Eq[F[Either[A, C], Either[B, C]]], - REqFEitherABC: Eq[F[Either[C, A], Either[C, B]]], - LEqFEitherABCD: Eq[F[Either[D, A], Either[D, C]]], - REqFEitherABCD: Eq[F[Either[A, D], Either[C, D]]] + REqFEitherABCD: Eq[F[Either[A, C], Either[B, D]]], + EitherAssociationABC: Eq[F[Either[Either[A, B], C], Either[D, Either[B, C]]]] ): RuleSet = new RuleSet { def name: String = "arrowChoice" @@ -53,11 +53,10 @@ trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { ) def props: Seq[(String, Prop)] = Seq( "left and lift commute" -> forAll(laws.leftLiftCommute[A, B, C] _), - "right and lift commute" -> forAll(laws.rightLiftCommute[A, B, C] _), - "choose and lift commute" -> forAll(laws.chooseLiftCommute[A, B, C, D] _), - "choice and lift commute" -> forAll(laws.choiceLiftCommute[A, B, C] _), "left and compose commute" -> forAll(laws.leftComposeCommute[A, B, C, D] _), - "right and compose commute" -> forAll(laws.rightComposeCommute[A, B, C, D] _) + "left and then lift (Left.apply) commutes" -> forAll(laws.leftAndThenLiftedLeftApplyCommutes[A, B, C] _), + "left and then identity +++ _ commutes" -> forAll(laws.leftAndThenRightIdentityCommutes[A, B, C, D] _), + "left commutes with sum association" -> forAll(laws.leftTwiceCommutesWithSumAssociation[A, B, C, D] _) ) } } From 191c68cde6ccedb8f8ad74cd65de8af499d044e7 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Tue, 12 Dec 2017 20:14:10 -0800 Subject: [PATCH 6/7] Clean up laws code --- laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala index 10844dae80..fc8af6ef51 100644 --- a/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala +++ b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala @@ -32,8 +32,8 @@ trait ArrowChoiceLaws[F[_, _]] extends ArrowLaws[F] with ChoiceLaws[F] { (F.left(f) >>> F.lift(identity[B] _ +++ g)) <-> (F.lift(identity[A] _ +++ g) >>> F.left(f)) def leftTwiceCommutesWithSumAssociation[A, B, C, D](f: F[A, D]): IsEq[F[Either[Either[A, B], C], Either[D, Either[B, C]]]] = - (F.left[Either[A, B], Either[D, B], C](F.left[A, D, B](f)) >>> F.lift[Either[Either[D, B], C], Either[D, Either[B, C]]](sumAssoc[D, B, C])) <-> - (F.lift[Either[Either[A, B], C], Either[A, Either[B, C]]](sumAssoc[A, B, C]) >>> F.left(f)) + (F.left(F.left[A, D, B](f)) >>> F.lift(sumAssoc[D, B, C])) <-> + (F.lift(sumAssoc[A, B, C]) >>> F.left(f)) } object ArrowChoiceLaws { From 8b5d4972f83b452f1c8da7228bdf76c058491057 Mon Sep 17 00:00:00 2001 From: Stephen Lazaro Date: Wed, 13 Dec 2017 11:23:19 -0800 Subject: [PATCH 7/7] Consistency law, match over fold --- core/src/main/scala/cats/instances/function.scala | 8 ++++---- laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala | 5 +++++ .../scala/cats/laws/discipline/ArrowChoiceTests.scala | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 5960d9b3d1..b711fb37a4 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -79,10 +79,10 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 { implicit val catsStdInstancesForFunction1: ArrowChoice[Function1] with CommutativeArrow[Function1] = new ArrowChoice[Function1] with CommutativeArrow[Function1] { def choose[A, B, C, D](f: A => C)(g: B => D): Either[A, B] => Either[C, D] = - _.fold( - Left.apply[C, D] _ compose f, - Right.apply[C, D] _ compose g - ) + _ match { + case Left(a) => Left(f(a)) + case Right(b) => Right(g(b)) + } def lift[A, B](f: A => B): A => B = f diff --git a/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala index fc8af6ef51..b6b2546e82 100644 --- a/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala +++ b/laws/src/main/scala/cats/laws/ArrowChoiceLaws.scala @@ -4,6 +4,7 @@ package laws import cats.arrow.ArrowChoice import cats.syntax.arrowChoice._ import cats.syntax.compose._ +import cats.syntax.profunctor._ /** * Laws that must be obeyed by any `cats.arrow.ArrowChoice`. @@ -25,6 +26,10 @@ trait ArrowChoiceLaws[F[_, _]] extends ArrowLaws[F] with ChoiceLaws[F] { def leftComposeCommute[A, B, C, D](f: F[A, B], g: F[B, C]): IsEq[F[Either[A, D], Either[C, D]]] = F.left(f >>> g) <-> (F.left(f) >>> F.left[B, C, D](g)) + def leftRightConsistent[A, B, C](f: A => B): IsEq[F[Either[C, A], Either[C, B]]] = + F.right[A, B, C](F.lift[A, B](f)) <-> + F.left[A, B, C](F.lift[A, B](f)).dimap((x: Either[C, A]) => x.swap)((y: Either[B, C]) => y.swap) + def leftAndThenLiftedLeftApplyCommutes[A, B, C](f: F[A, B]): IsEq[F[A, Either[B, C]]] = (f >>> F.lift[B, Either[B, C]](Left.apply[B, C])) <-> (F.lift[A, Either[A, C]](Left.apply[A, C] _) >>> F.left(f)) diff --git a/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala index 8628d756fd..8a2c61f181 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArrowChoiceTests.scala @@ -41,6 +41,7 @@ trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { EqFEitherCoABC: Eq[F[A, Either[B, C]]], REqFEitherACD: Eq[F[Either[A, D], Either[C, D]]], LEqFEitherABC: Eq[F[Either[A, C], Either[B, C]]], + REqFEitherABC: Eq[F[Either[C, A], Either[C, B]]], REqFEitherABCD: Eq[F[Either[A, C], Either[B, D]]], EitherAssociationABC: Eq[F[Either[Either[A, B], C], Either[D, Either[B, C]]]] ): RuleSet = @@ -54,6 +55,7 @@ trait ArrowChoiceTests[F[_, _]] extends ArrowTests[F] with ChoiceTests[F] { def props: Seq[(String, Prop)] = Seq( "left and lift commute" -> forAll(laws.leftLiftCommute[A, B, C] _), "left and compose commute" -> forAll(laws.leftComposeCommute[A, B, C, D] _), + "left and right consistent" -> forAll(laws.leftRightConsistent[A, B, C] _), "left and then lift (Left.apply) commutes" -> forAll(laws.leftAndThenLiftedLeftApplyCommutes[A, B, C] _), "left and then identity +++ _ commutes" -> forAll(laws.leftAndThenRightIdentityCommutes[A, B, C, D] _), "left commutes with sum association" -> forAll(laws.leftTwiceCommutesWithSumAssociation[A, B, C, D] _)