From 8c95312e1b7220b69ff9ad0d1431235928950677 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 24 Sep 2017 17:45:07 +0200 Subject: [PATCH] Add CommutativeApply and Applicative and an instance for Validated[CommutativeSemigroup, ?] --- .../scala/cats/CommutativeApplicative.scala | 14 ++++++ .../main/scala/cats/CommutativeApply.scala | 14 ++++++ .../main/scala/cats/CommutativeFlatMap.scala | 2 +- .../main/scala/cats/CommutativeMonad.scala | 2 +- core/src/main/scala/cats/data/Validated.scala | 15 +++++++ .../laws/CommutativeApplicativeLaws.scala | 12 +++++ .../cats/laws/CommutativeApplyLaws.scala | 19 ++++++++ .../cats/laws/CommutativeFlatMapLaws.scala | 2 +- .../cats/laws/CommutativeMonadLaws.scala | 2 +- .../CommutativeApplicativeTests.scala | 42 +++++++++++++++++ .../discipline/CommutativeApplyTests.scala | 45 +++++++++++++++++++ .../discipline/CommutativeFlatMapTests.scala | 4 +- .../discipline/CommutativeMonadTests.scala | 4 +- .../scala/cats/tests/ValidatedTests.scala | 2 + 14 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 core/src/main/scala/cats/CommutativeApplicative.scala create mode 100644 core/src/main/scala/cats/CommutativeApply.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala new file mode 100644 index 00000000000..99ca3fcdfcf --- /dev/null +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative Applicative. + * + * Further than an Applicative, which just allows composition of independent effectful functions, + * in a Commutative Applicative those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeApplicativeLaws. + */ +@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] diff --git a/core/src/main/scala/cats/CommutativeApply.scala b/core/src/main/scala/cats/CommutativeApply.scala new file mode 100644 index 00000000000..13a1974211d --- /dev/null +++ b/core/src/main/scala/cats/CommutativeApply.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative Apply. + * + * Further than an Apply, which just allows composition of independent effectful functions, + * in a Commutative Apply those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeApplyLaws. + */ +@typeclass trait CommutativeApply[F[_]] extends Apply[F] diff --git a/core/src/main/scala/cats/CommutativeFlatMap.scala b/core/src/main/scala/cats/CommutativeFlatMap.scala index b7293de06b7..22998bb3db9 100644 --- a/core/src/main/scala/cats/CommutativeFlatMap.scala +++ b/core/src/main/scala/cats/CommutativeFlatMap.scala @@ -11,4 +11,4 @@ import simulacrum.typeclass * * Must obey the laws defined in cats.laws.CommutativeFlatMapLaws. */ -@typeclass trait CommutativeFlatMap[F[_]] extends FlatMap[F] +@typeclass trait CommutativeFlatMap[F[_]] extends FlatMap[F] with CommutativeApply[F] diff --git a/core/src/main/scala/cats/CommutativeMonad.scala b/core/src/main/scala/cats/CommutativeMonad.scala index 369541142a1..9c7b07792d7 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] with CommutativeFlatMap[F] +@typeclass trait CommutativeMonad[F[_]] extends Monad[F] with CommutativeFlatMap[F] with CommutativeApplicative[F] diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index d195a7634df..1ba25e46c03 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -2,6 +2,7 @@ package cats package data import cats.data.Validated.{Invalid, Valid} +import cats.kernel.CommutativeSemigroup import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} @@ -374,6 +375,20 @@ private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstanc def combine(x: Validated[A, B], y: Validated[A, B]): Validated[A, B] = x combine y } + implicit def catsDataCommutativeApplicativeForValidated[E: CommutativeSemigroup]: CommutativeApplicative[Validated[E, ?]] = + new CommutativeApplicative[Validated[E, ?]] { + override def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = + fa.map(f) + + def pure[A](a: A): Validated[E, A] = Validated.valid(a) + + def ap[A, B](ff: Validated[E, (A) => B])(fa: Validated[E, A]): Validated[E, B] = + fa.ap(ff)(Semigroup[E]) + + override def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = + fa.product(fb)(Semigroup[E]) + } + implicit def catsDataPartialOrderForValidated[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A, B]] = new PartialOrder[Validated[A, B]] { def partialCompare(x: Validated[A, B], y: Validated[A, B]): Double = x partialCompare y diff --git a/laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala b/laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala new file mode 100644 index 00000000000..05f97703127 --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala @@ -0,0 +1,12 @@ +package cats.laws + +import cats.CommutativeApplicative + +trait CommutativeApplicativeLaws[F[_]] extends CommutativeApplyLaws[F] with ApplicativeLaws[F] { + implicit override def F: CommutativeApplicative[F] +} + +object CommutativeApplicativeLaws { + def apply[F[_]](implicit ev: CommutativeApplicative[F]): CommutativeApplicativeLaws[F] = + new CommutativeApplicativeLaws[F] { def F: CommutativeApplicative[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala b/laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala new file mode 100644 index 00000000000..cbc2a54e3e6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala @@ -0,0 +1,19 @@ +package cats.laws + +import cats.CommutativeApply + +/** + * Laws that must be obeyed by any `CommutativeApply`. + */ +trait CommutativeApplyLaws[F[_]] extends ApplyLaws[F] { + implicit override def F: CommutativeApply[F] + + def applyCommutative[A, B, C](fa: F[A], fb: F[B], f: (A, B) => C): IsEq[F[C]] = + F.map2(fa, fb)(f) <-> F.map2(fb, fa)((b, a) => f(a, b)) + +} + +object CommutativeApplyLaws { + def apply[F[_]](implicit ev: CommutativeApply[F]): CommutativeApplyLaws[F] = + new CommutativeApplyLaws[F] { def F: CommutativeApply[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala b/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala index 321183097c9..1ed46cd511b 100644 --- a/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala +++ b/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala @@ -4,7 +4,7 @@ package laws /** * Laws that must be obeyed by any `CommutativeFlatMap`. */ -trait CommutativeFlatMapLaws[F[_]] extends FlatMapLaws[F] { +trait CommutativeFlatMapLaws[F[_]] extends CommutativeApplyLaws[F] with 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]] = diff --git a/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala index 1172b30165a..7333a994efb 100644 --- a/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala +++ b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala @@ -4,7 +4,7 @@ package laws /** * Laws that must be obeyed by any `CommutativeMonad`. */ -trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] with CommutativeFlatMapLaws[F] { +trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] with CommutativeFlatMapLaws[F] with CommutativeApplicativeLaws[F] { implicit override def F: CommutativeMonad[F] } diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala new file mode 100644 index 00000000000..4b1359d04e7 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala @@ -0,0 +1,42 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.CartesianTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} + +trait CommutativeApplicativeTests[F[_]] extends CommutativeApplyTests[F] with ApplicativeTests[F] { + + def laws: CommutativeApplicativeLaws[F] + + def commutativeApplicative[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 applicative" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(applicative[A, B, C], commutativeApply[A, B, C]) + def props: Seq[(String, Prop)] = Nil + } + } +} + +object CommutativeApplicativeTests { + def apply[F[_]: CommutativeApplicative]: CommutativeApplicativeTests[F] = + new CommutativeApplicativeTests[F] { + def laws: CommutativeApplicativeLaws[F] = CommutativeApplicativeLaws[F] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala new file mode 100644 index 00000000000..4946a90f64f --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.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 CommutativeApplyTests[F[_]] extends ApplyTests[F] { + def laws: CommutativeApplyLaws[F] + + def commutativeApply[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 apply" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(apply[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "apply commutativity" -> forAll(laws.applyCommutative[A, B, C] _) + ) + } + } + +} + +object CommutativeApplyTests { + def apply[F[_]: CommutativeFlatMap]: CommutativeApplyTests[F] = + new CommutativeApplyTests[F] { + def laws: CommutativeApplyLaws[F] = CommutativeApplyLaws[F] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala index 70a69fe2bd1..87e1168bd8b 100644 --- a/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala @@ -6,7 +6,7 @@ import cats.laws.discipline.CartesianTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait CommutativeFlatMapTests[F[_]] extends FlatMapTests[F] { +trait CommutativeFlatMapTests[F[_]] extends FlatMapTests[F] with CommutativeApplyTests[F] { def laws: CommutativeFlatMapLaws[F] def commutativeFlatMap[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -28,7 +28,7 @@ trait CommutativeFlatMapTests[F[_]] extends FlatMapTests[F] { new RuleSet { def name: String = "commutative flatMap" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(flatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(flatMap[A, B, C], commutativeApply[A, B, C]) def props: Seq[(String, Prop)] = Seq( "flatmap commutativity" -> forAll(laws.flatmapCommutative[A, B, C] _) ) diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala index c2a09757597..4114e75d5b8 100644 --- a/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala @@ -5,7 +5,7 @@ package discipline import cats.laws.discipline.CartesianTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} -trait CommutativeMonadTests[F[_]] extends MonadTests[F] with CommutativeFlatMapTests[F] { +trait CommutativeMonadTests[F[_]] extends MonadTests[F] with CommutativeFlatMapTests[F] with CommutativeApplicativeTests[F] { def laws: CommutativeMonadLaws[F] def commutativeMonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -27,7 +27,7 @@ trait CommutativeMonadTests[F[_]] extends MonadTests[F] with CommutativeFlatMapT new RuleSet { def name: String = "commutative monad" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(monad[A, B, C], commutativeFlatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(monad[A, B, C], commutativeFlatMap[A, B, C], commutativeApplicative[A, B, C]) def props: Seq[(String, Prop)] = Nil } } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index dc152a5340d..4ebcda0e2dd 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -34,6 +34,8 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, NonEmptyList[Int]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) + checkAll("Validated[Int, Int]", CommutativeApplicativeTests[Validated[Int, ?]].commutativeApplicative[Int, Int, Int]) + { implicit val L = ListWrapper.semigroup[String] checkAll("Validated[ListWrapper[String], ?]", SemigroupKTests[Validated[ListWrapper[String], ?]].semigroupK[Int])