From 444e3bd6050ca36332dafb6a597eb6ebd6c5853f Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 00:47:54 -0500 Subject: [PATCH 01/46] Introduce Selective --- core/src/main/scala/cats/Monad.scala | 8 +++++++- core/src/main/scala/cats/Selective.scala | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/cats/Selective.scala diff --git a/core/src/main/scala/cats/Monad.scala b/core/src/main/scala/cats/Monad.scala index b5a12da490..33b97aae56 100644 --- a/core/src/main/scala/cats/Monad.scala +++ b/core/src/main/scala/cats/Monad.scala @@ -13,10 +13,16 @@ import scala.annotation.implicitNotFound * Must obey the laws defined in cats.laws.MonadLaws. */ @implicitNotFound("Could not find an instance of Monad for ${F}") -@typeclass trait Monad[F[_]] extends FlatMap[F] with Applicative[F] { +@typeclass trait Monad[F[_]] extends FlatMap[F] with Selective[F] { override def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a))) + override def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = + flatMap(fab) { + case Left(a) => map(ff)(_(a)) + case Right(b) => pure(b) + } + /** * Execute an action repeatedly as long as the given `Boolean` expression * returns `true`. The condition is evaluated before the loop body. diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala new file mode 100644 index 0000000000..7378d538f3 --- /dev/null +++ b/core/src/main/scala/cats/Selective.scala @@ -0,0 +1,17 @@ +package cats + +import simulacrum.{typeclass} +import scala.annotation.implicitNotFound + +@implicitNotFound("Could not find an instance of Selective for ${F}") +@typeclass trait Selective[F[_]] extends Applicative[F] { + def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] +} + +object Selective { + def fromApplicative[F[_], A, B](fab: F[Either[A, B]])(ff: F[A => B])(implicit F: Applicative[F]): F[B] = + F.map2(fab, ff) { + case (Left(a), f) => f(a) + case (Right(b), f) => b + } +} From da7f756fb3ed2adca18471e08c3b1d2fa015b24f Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 01:00:02 -0500 Subject: [PATCH 02/46] Split into Select and Selective --- core/src/main/scala/cats/Select.scala | 17 +++++++++++++++++ core/src/main/scala/cats/Selective.scala | 12 +----------- 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 core/src/main/scala/cats/Select.scala diff --git a/core/src/main/scala/cats/Select.scala b/core/src/main/scala/cats/Select.scala new file mode 100644 index 0000000000..2a7da821e9 --- /dev/null +++ b/core/src/main/scala/cats/Select.scala @@ -0,0 +1,17 @@ +package cats + +import simulacrum.{typeclass} +import scala.annotation.implicitNotFound + +@implicitNotFound("Could not find an instance of Select for ${F}") +@typeclass trait Select[F[_]] extends Apply[F] { + def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] +} + +object Select { + def fromApply[F[_], A, B](fab: F[Either[A, B]])(ff: F[A => B])(implicit F: Apply[F]): F[B] = + F.map2(fab, ff) { + case (Left(a), f) => f(a) + case (Right(b), f) => b + } +} diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 7378d538f3..fbc5e961dc 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -4,14 +4,4 @@ import simulacrum.{typeclass} import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") -@typeclass trait Selective[F[_]] extends Applicative[F] { - def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] -} - -object Selective { - def fromApplicative[F[_], A, B](fab: F[Either[A, B]])(ff: F[A => B])(implicit F: Applicative[F]): F[B] = - F.map2(fab, ff) { - case (Left(a), f) => f(a) - case (Right(b), f) => b - } -} +@typeclass trait Selective[F[_]] extends Applicative[F] with Select[F] From d2203493bcbf4d5f3b62c3281a7b33080dcd0cbf Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 01:35:09 -0500 Subject: [PATCH 03/46] Selective[Validated[E, *]] --- core/src/main/scala/cats/data/EitherT.scala | 4 +-- core/src/main/scala/cats/data/Validated.scala | 28 +++++++++++++++---- .../main/scala/cats/instances/either.scala | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index fa581de3ac..6dc7ba70be 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -922,7 +922,7 @@ abstract private[data] class EitherTInstances extends EitherTInstances1 { implicit val monadEither: Monad[Either[E, *]] = cats.instances.either.catsStdInstancesForEither def applicative: Applicative[Nested[P.F, Validated[E, *], *]] = - cats.data.Nested.catsDataApplicativeForNested(P.applicative, Validated.catsDataApplicativeErrorForValidated) + cats.data.Nested.catsDataApplicativeForNested(P.applicative, Validated.catsDataSelectiveErrorForValidated) def monad: Monad[EitherT[M, E, *]] = cats.data.EitherT.catsDataMonadErrorForEitherT @@ -983,7 +983,7 @@ abstract private[data] class EitherTInstances1 extends EitherTInstances2 { new Parallel[EitherT[M, E, *]] { type F[x] = Nested[M, Validated[E, *], x] - implicit val appValidated: Applicative[Validated[E, *]] = Validated.catsDataApplicativeErrorForValidated + implicit val appValidated: Applicative[Validated[E, *]] = Validated.catsDataSelectiveErrorForValidated implicit val monadEither: Monad[Either[E, *]] = cats.instances.either.catsStdInstancesForEither def applicative: Applicative[Nested[M, Validated[E, *], *]] = diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 51100aae05..3c989affbe 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -931,9 +931,8 @@ sealed abstract private[data] class ValidatedInstances extends ValidatedInstance fab.leftMap(f) } - implicit def catsDataApplicativeErrorForValidated[E](implicit E: Semigroup[E]): ApplicativeError[Validated[E, *], E] = - new ValidatedApplicative[E] with ApplicativeError[Validated[E, *], E] { - + implicit def catsDataSelectiveErrorForValidated[E](implicit E: Semigroup[E]): Selective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = + new ValidatedSelective[E] with ApplicativeError[Validated[E, *], E] { def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = fa match { case Validated.Invalid(e) => f(e) @@ -941,6 +940,10 @@ sealed abstract private[data] class ValidatedInstances extends ValidatedInstance } def raiseError[A](e: E): Validated[E, A] = Validated.Invalid(e) } + + @deprecated("Use catsDataSelectiveErrorForValidated", "2.4.0") + def catsDataApplicativeErrorForValidated[E](implicit E: Semigroup[E]): ApplicativeError[Validated[E, *], E] = + catsDataSelectiveErrorForValidated } sealed abstract private[data] class ValidatedInstances1 extends ValidatedInstances2 { @@ -953,9 +956,13 @@ sealed abstract private[data] 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 ValidatedApplicative[E] with CommutativeApplicative[Validated[E, *]] + implicit def catsDataCommutativeSelectiveForValidated[E: CommutativeSemigroup] + : Selective[Validated[E, *]] with CommutativeApplicative[Validated[E, *]] = + new ValidatedSelective[E] with CommutativeApplicative[Validated[E, *]] + + @deprecated("Use catsDataCommutativeSelectiveForValidated", "2.4.0") + def catsDataCommutativeApplicativeForValidated[E: CommutativeSemigroup]: CommutativeApplicative[Validated[E, *]] = + catsDataCommutativeApplicativeForValidated implicit def catsDataPartialOrderForValidated[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A, B]] = new PartialOrder[Validated[A, B]] { @@ -1035,6 +1042,15 @@ sealed abstract private[data] class ValidatedInstances2 { // scalastyle:off method.length } +private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] with Selective[Validated[E, *]] { + override def select[A, B](fab: Validated[E, Either[A, B]])(ff: Validated[E, A => B]): Validated[E, B] = + fab match { + case Valid(Left(a)) => ff.map(_(a)) + case Valid(Right(b)) => Valid(b) + case i @ Invalid(_) => i + } +} + private[data] class ValidatedApplicative[E: Semigroup] extends CommutativeApplicative[Validated[E, *]] { override def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = fa.map(f) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index a558fd503d..9830342235 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -207,7 +207,7 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { new Parallel[Either[E, *]] { type F[x] = Validated[E, x] - def applicative: Applicative[Validated[E, *]] = Validated.catsDataApplicativeErrorForValidated + def applicative: Applicative[Validated[E, *]] = Validated.catsDataSelectiveErrorForValidated def monad: Monad[Either[E, *]] = cats.instances.either.catsStdInstancesForEither def sequential: Validated[E, *] ~> Either[E, *] = From 4c7f87ef23602061bbdd82e62fd5a29cb4de2105 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 01:52:15 -0500 Subject: [PATCH 04/46] SelectiveError --- core/src/main/scala/cats/MonadError.scala | 8 +------- core/src/main/scala/cats/SelectiveError.scala | 15 +++++++++++++++ core/src/main/scala/cats/data/Validated.scala | 4 ++-- core/src/main/scala/cats/syntax/either.scala | 6 ++++++ 4 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 core/src/main/scala/cats/SelectiveError.scala diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 97e8264250..90e456d570 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -5,13 +5,7 @@ package cats * * This type class allows one to abstract over error-handling monads. */ -trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { - - /** - * Turns a successful value into an error if it does not satisfy a given predicate. - */ - def ensure[A](fa: F[A])(error: => E)(predicate: A => Boolean): F[A] = - flatMap(fa)(a => if (predicate(a)) pure(a) else raiseError(error)) +trait MonadError[F[_], E] extends SelectiveError[F, E] with Monad[F] { /** * Turns a successful value into an error specified by the `error` function if it does not satisfy a given predicate. diff --git a/core/src/main/scala/cats/SelectiveError.scala b/core/src/main/scala/cats/SelectiveError.scala new file mode 100644 index 0000000000..1335f2c5be --- /dev/null +++ b/core/src/main/scala/cats/SelectiveError.scala @@ -0,0 +1,15 @@ +package cats + +import cats.syntax.either._ + +trait SelectiveError[F[_], E] extends ApplicativeError[F, E] with Selective[F] { + + /** + * Turns a successful value into an error if it does not satisfy a given predicate. + */ + def ensure[A](fa: F[A])(error: => E)(predicate: A => Boolean): F[A] = + select(map(fa) { a: A => + if (predicate(a)) Right(a) + else Either.leftUnit + })(raiseError(error)) +} diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 3c989affbe..64b7411435 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -931,8 +931,8 @@ sealed abstract private[data] class ValidatedInstances extends ValidatedInstance fab.leftMap(f) } - implicit def catsDataSelectiveErrorForValidated[E](implicit E: Semigroup[E]): Selective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = - new ValidatedSelective[E] with ApplicativeError[Validated[E, *], E] { + implicit def catsDataSelectiveErrorForValidated[E](implicit E: Semigroup[E]): SelectiveError[Validated[E, *], E] = + new ValidatedSelective[E] with SelectiveError[Validated[E, *], E] { def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = fa match { case Validated.Invalid(e) => f(e) diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index 534d4117f6..f13bf5d788 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -395,6 +395,11 @@ final class EitherObjectOps(private val either: Either.type) extends AnyVal { // * Cached value of `Right(())` to avoid allocations for a common case. */ def unit[A]: Either[A, Unit] = EitherUtil.unit + + /** + * Cached value of `Left(())` to avoid allocations for a common case. + */ + def leftUnit[B]: Either[Unit, B] = EitherUtil.leftUnit } final class LeftOps[A, B](private val left: Left[A, B]) extends AnyVal { @@ -509,4 +514,5 @@ private[cats] object EitherUtil { left.asInstanceOf[Either[A, C]] private[cats] val unit = Right(()) + private[cats] val leftUnit = Left(()) } From 14d3e429e92f0982be8ec25799138e30efddf9f4 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 01:55:44 -0500 Subject: [PATCH 05/46] FlatMap extends Select --- core/src/main/scala/cats/FlatMap.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index fe963c5999..16eb79dac5 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -20,7 +20,7 @@ import scala.annotation.implicitNotFound * Must obey the laws defined in cats.laws.FlatMapLaws. */ @implicitNotFound("Could not find an instance of FlatMap for ${F}") -@typeclass trait FlatMap[F[_]] extends Apply[F] { +@typeclass trait FlatMap[F[_]] extends Select[F] { def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] /** From b7112acd085909f643bdb2a15725fd54efb378e6 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 09:47:32 -0500 Subject: [PATCH 06/46] Required parens on lambda --- core/src/main/scala/cats/SelectiveError.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/SelectiveError.scala b/core/src/main/scala/cats/SelectiveError.scala index 1335f2c5be..61a963c890 100644 --- a/core/src/main/scala/cats/SelectiveError.scala +++ b/core/src/main/scala/cats/SelectiveError.scala @@ -8,7 +8,7 @@ trait SelectiveError[F[_], E] extends ApplicativeError[F, E] with Selective[F] { * Turns a successful value into an error if it does not satisfy a given predicate. */ def ensure[A](fa: F[A])(error: => E)(predicate: A => Boolean): F[A] = - select(map(fa) { a: A => + select(map(fa) { (a: A) => if (predicate(a)) Right(a) else Either.leftUnit })(raiseError(error)) From b8dc63bd44fd15ba44c94d2c9f30b114b3d9d055 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 10:04:31 -0500 Subject: [PATCH 07/46] Default select from Apply until Monad --- core/src/main/scala/cats/Select.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/Select.scala b/core/src/main/scala/cats/Select.scala index 2a7da821e9..879c08282a 100644 --- a/core/src/main/scala/cats/Select.scala +++ b/core/src/main/scala/cats/Select.scala @@ -5,12 +5,8 @@ import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Select for ${F}") @typeclass trait Select[F[_]] extends Apply[F] { - def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] -} - -object Select { - def fromApply[F[_], A, B](fab: F[Either[A, B]])(ff: F[A => B])(implicit F: Apply[F]): F[B] = - F.map2(fab, ff) { + def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = + map2(fab, ff) { case (Left(a), f) => f(a) case (Right(b), f) => b } From 7fd409b54a0efab8437344f6c90374387fe2ffc8 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 12:42:21 -0500 Subject: [PATCH 08/46] Start writing SelectiveLaws --- laws/src/main/scala/cats/laws/MonadLaws.scala | 2 +- .../main/scala/cats/laws/SelectiveLaws.scala | 24 ++++++++++ .../cats/laws/discipline/MonadTests.scala | 24 +++++++--- .../cats/laws/discipline/SelectiveTests.scala | 46 +++++++++++++++++++ 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/SelectiveLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala diff --git a/laws/src/main/scala/cats/laws/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala index b488192ae6..7ce17ba148 100644 --- a/laws/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadLaws.scala @@ -7,7 +7,7 @@ import cats.implicits._ /** * Laws that must be obeyed by any `Monad`. */ -trait MonadLaws[F[_]] extends ApplicativeLaws[F] with FlatMapLaws[F] { +trait MonadLaws[F[_]] extends SelectiveLaws[F] with FlatMapLaws[F] { implicit override def F: Monad[F] def monadLeftIdentity[A, B](a: A, f: A => F[B]): IsEq[F[B]] = diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala new file mode 100644 index 0000000000..bebaf99f44 --- /dev/null +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -0,0 +1,24 @@ +package cats +package laws + +import cats.syntax.functor._ + +/** + * Laws that must be obeyed by any `Selective`. + */ +trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { + implicit override def F: Selective[F] + + def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = + F.select(faa)(F.pure(identity)) <-> faa.map(_.merge) + + def selectiveDistributivity(ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[A]] = + F.select(F.pure(fab))(ff1 *> ff2) <-> F.select(pure(fab))(fa1) *> F.select(pure(fab))(fa2) + + // TODO associativity +} + +object SelectiveLaws { + def apply[F[_]](implicit ev: Selective[F]): SelectiveLaws[F] = + new SelectiveLaws[F] { def F: Selective[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index 82459d34d6..4b26be4e8a 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -4,10 +4,11 @@ package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.platform.Platform -import org.scalacheck.{Arbitrary, Cogen, Prop} +import cats.syntax.all._ +import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} import Prop._ -trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { +trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { def laws: MonadLaws[F] def monad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -25,11 +26,16 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { EqFABC: Eq[F[(A, B, C)]], EqFInt: Eq[F[Int]], iso: Isomorphisms[F] - ): RuleSet = + ): RuleSet = { + implicit def ArbFAA: Arbitrary[F[Either[A, A]]] = + Arbitrary(Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])))) + new RuleSet { def name: String = "monad" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(applicative[A, B, C], flatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(selective[A, B, C], flatMap[A, B, C]) def props: Seq[(String, Prop)] = Seq( "monad left identity" -> forAll(laws.monadLeftIdentity[A, B] _), @@ -38,6 +44,7 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { ) ++ (if (Platform.isJvm) Seq[(String, Prop)]("tailRecM stack safety" -> Prop.lzy(laws.tailRecMStackSafety)) else Seq.empty) } + } def stackUnsafeMonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit ArbFA: Arbitrary[F[A]], @@ -54,11 +61,15 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { EqFABC: Eq[F[(A, B, C)]], EqFInt: Eq[F[Int]], iso: Isomorphisms[F] - ): RuleSet = + ): RuleSet = { + implicit def ArbFAA: Arbitrary[F[Either[A, A]]] = + Arbitrary(Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])))) new RuleSet { def name: String = "monad (stack-unsafe)" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(applicative[A, B, C], flatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(selective[A, B, C], flatMap[A, B, C]) def props: Seq[(String, Prop)] = Seq( "monad left identity" -> forAll(laws.monadLeftIdentity[A, B] _), @@ -66,6 +77,7 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { "map flatMap coherence" -> forAll(laws.mapFlatMapCoherence[A, B] _) ) } + } } object MonadTests { diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala new file mode 100644 index 0000000000..798c7f9890 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -0,0 +1,46 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.SemigroupalTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait SelectiveTests[F[_]] extends ApplicativeTests[F] { + def laws: SelectiveLaws[F] + + def selective[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]], + ArbFAA: Arbitrary[F[Either[A, A]]], + 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 = "selective" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) + def props: Seq[(String, Prop)] = + Seq( + "selective identity" -> forAll(laws.selectiveIdentity[A, B] _) + ) + } + } +} + +object SelectiveTests { + def apply[F[_]: Selective]: SelectiveTests[F] = + new SelectiveTests[F] { + def laws: SelectiveLaws[F] = SelectiveLaws[F] + } +} From 254bf7f90432517a8d9c8f3b63d467adb17b5c19 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 13:13:13 -0500 Subject: [PATCH 09/46] Look at me, not compiling before I push --- .../src/main/scala/cats/laws/SelectiveLaws.scala | 4 ++-- .../scala/cats/laws/discipline/MonadTests.scala | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index bebaf99f44..c7882de228 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -12,8 +12,8 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = F.select(faa)(F.pure(identity)) <-> faa.map(_.merge) - def selectiveDistributivity(ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[A]] = - F.select(F.pure(fab))(ff1 *> ff2) <-> F.select(pure(fab))(fa1) *> F.select(pure(fab))(fa2) + def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = + F.select(F.pure(ab))(F.productR(ff1)(ff2)) <-> F.productR(F.select(F.pure(ab))(ff1))(F.select(F.pure(ab))(ff2)) // TODO associativity } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index 4b26be4e8a..a1a813a136 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -28,9 +28,11 @@ trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { iso: Isomorphisms[F] ): RuleSet = { implicit def ArbFAA: Arbitrary[F[Either[A, A]]] = - Arbitrary(Gen.oneOf( - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])))) + Arbitrary( + Gen.oneOf(ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) + ) + ) new RuleSet { def name: String = "monad" @@ -63,9 +65,11 @@ trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { iso: Isomorphisms[F] ): RuleSet = { implicit def ArbFAA: Arbitrary[F[Either[A, A]]] = - Arbitrary(Gen.oneOf( - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])))) + Arbitrary( + Gen.oneOf(ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) + ) + ) new RuleSet { def name: String = "monad (stack-unsafe)" def bases: Seq[(String, RuleSet)] = Nil From ed71f060296e9250a134030f21e6c15ae22cf69f Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 13:43:23 -0500 Subject: [PATCH 10/46] Never mind on Select --- core/src/main/scala/cats/Apply.scala | 6 ++++++ core/src/main/scala/cats/FlatMap.scala | 2 +- core/src/main/scala/cats/Select.scala | 13 ------------- core/src/main/scala/cats/Selective.scala | 2 +- 4 files changed, 8 insertions(+), 15 deletions(-) delete mode 100644 core/src/main/scala/cats/Select.scala diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index fb64a1b93f..33149ead79 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -257,6 +257,12 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse ap2(map(fcond)(ite))(ifTrue, ifFalse) } + + def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = + map2(fab, ff) { + case (Left(a), f) => f(a) + case (Right(b), f) => b + } } object Apply { diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index a5c02b7beb..cc7144efaa 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -20,7 +20,7 @@ import scala.annotation.implicitNotFound * Must obey the laws defined in cats.laws.FlatMapLaws. */ @implicitNotFound("Could not find an instance of FlatMap for ${F}") -@typeclass trait FlatMap[F[_]] extends Select[F] { +@typeclass trait FlatMap[F[_]] extends Apply[F] { def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] /** diff --git a/core/src/main/scala/cats/Select.scala b/core/src/main/scala/cats/Select.scala deleted file mode 100644 index 879c08282a..0000000000 --- a/core/src/main/scala/cats/Select.scala +++ /dev/null @@ -1,13 +0,0 @@ -package cats - -import simulacrum.{typeclass} -import scala.annotation.implicitNotFound - -@implicitNotFound("Could not find an instance of Select for ${F}") -@typeclass trait Select[F[_]] extends Apply[F] { - def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = - map2(fab, ff) { - case (Left(a), f) => f(a) - case (Right(b), f) => b - } -} diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index fbc5e961dc..0d29064cf1 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -4,4 +4,4 @@ import simulacrum.{typeclass} import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") -@typeclass trait Selective[F[_]] extends Applicative[F] with Select[F] +@typeclass trait Selective[F[_]] extends Applicative[F] From 7767906f139e3b76798fd6f63927913acab3b16f Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 9 Dec 2020 14:03:53 -0500 Subject: [PATCH 11/46] branch, ifS, whenS --- core/src/main/scala/cats/Apply.scala | 16 ++++++++++++++++ core/src/main/scala/cats/Selective.scala | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index 33149ead79..a2dec37488 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -263,6 +263,22 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit case (Left(a), f) => f(a) case (Right(b), f) => b } + + def branch[A, B, C](x: F[Either[A, B]])(l: F[A => C])(r: F[B => C]): F[C] = { + val lhs = { + val innerLhs: F[Either[A, Either[B, C]]] = map(x)(_.map(Left(_))) + val innerRhs: F[A => Either[B, C]] = map(l)(_.andThen(Right(_))) + select(innerLhs)(innerRhs) + } + select(lhs)(r) + } + + def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { + val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) Left(()) else Right(())) + val left: F[Unit => A] = map(t)(Function.const) + val right: F[Unit => A] = map(e)(Function.const) + branch(condition)(left)(right) + } } object Apply { diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 0d29064cf1..18796fabfd 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -4,4 +4,7 @@ import simulacrum.{typeclass} import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") -@typeclass trait Selective[F[_]] extends Applicative[F] +@typeclass trait Selective[F[_]] extends Applicative[F] { + def whenS[A](fbool: F[Boolean])(fa: F[Unit]): F[Unit] = + ifS(fbool)(fa)(unit) +} From 466354be6bedb6db789570958627b35a4e144768 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 10 Dec 2020 01:18:43 -0500 Subject: [PATCH 12/46] select is an abstract member of Selective --- core/src/main/scala/cats/Applicative.scala | 1 - core/src/main/scala/cats/Apply.scala | 22 ---------------------- core/src/main/scala/cats/Selective.scala | 18 ++++++++++++++++++ 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index c57f2d3bce..c04a39bd35 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -185,7 +185,6 @@ import scala.annotation.implicitNotFound */ def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = if (cond) void(f) else unit - } object Applicative { diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index a2dec37488..fb64a1b93f 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -257,28 +257,6 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse ap2(map(fcond)(ite))(ifTrue, ifFalse) } - - def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = - map2(fab, ff) { - case (Left(a), f) => f(a) - case (Right(b), f) => b - } - - def branch[A, B, C](x: F[Either[A, B]])(l: F[A => C])(r: F[B => C]): F[C] = { - val lhs = { - val innerLhs: F[Either[A, Either[B, C]]] = map(x)(_.map(Left(_))) - val innerRhs: F[A => Either[B, C]] = map(l)(_.andThen(Right(_))) - select(innerLhs)(innerRhs) - } - select(lhs)(r) - } - - def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { - val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) Left(()) else Right(())) - val left: F[Unit => A] = map(t)(Function.const) - val right: F[Unit => A] = map(e)(Function.const) - branch(condition)(left)(right) - } } object Apply { diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 18796fabfd..bd50517d05 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -5,6 +5,24 @@ import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") @typeclass trait Selective[F[_]] extends Applicative[F] { + def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] + + def branch[A, B, C](x: F[Either[A, B]])(l: F[A => C])(r: F[B => C]): F[C] = { + val lhs = { + val innerLhs: F[Either[A, Either[B, C]]] = map(x)(_.map(Left(_))) + val innerRhs: F[A => Either[B, C]] = map(l)(_.andThen(Right(_))) + select(innerLhs)(innerRhs) + } + select(lhs)(r) + } + + def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { + val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) Left(()) else Right(())) + val left: F[Unit => A] = map(t)(Function.const) + val right: F[Unit => A] = map(e)(Function.const) + branch(condition)(left)(right) + } + def whenS[A](fbool: F[Boolean])(fa: F[Unit]): F[Unit] = ifS(fbool)(fa)(unit) } From f31020a6d9db37998571b4f8c09ff61574466a67 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 10 Dec 2020 01:25:51 -0500 Subject: [PATCH 13/46] Law for monad select rigidity --- laws/src/main/scala/cats/laws/MonadLaws.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/laws/src/main/scala/cats/laws/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala index 7ce17ba148..636d08ba61 100644 --- a/laws/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadLaws.scala @@ -41,6 +41,15 @@ trait MonadLaws[F[_]] extends SelectiveLaws[F] with FlatMapLaws[F] { val res = F.tailRecM(0)(i => F.pure(if (i < n) Either.left(i + 1) else Either.right(i))) res <-> F.pure(n) } + + def selectRigidity[A, B](fab: F[Either[A, B]], ff: F[A => B]): IsEq[F[B]] = { + def selectM[G[_]: Monad](gab: G[Either[A, B]])(gf: G[A => B]) = + gab.flatMap { + case Left(a) => gf.map(_(a)) + case Right(b) => b.pure[G] + } + F.select(fab)(ff) <-> selectM(fab)(ff) + } } object MonadLaws { From 4a2b0be9356c956a2f9b6737f75d0c031a15d2cf Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 10 Dec 2020 01:47:26 -0500 Subject: [PATCH 14/46] scalafmt --- laws/src/main/scala/cats/laws/MonadLaws.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala index 636d08ba61..02b6d467bb 100644 --- a/laws/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadLaws.scala @@ -45,7 +45,7 @@ trait MonadLaws[F[_]] extends SelectiveLaws[F] with FlatMapLaws[F] { def selectRigidity[A, B](fab: F[Either[A, B]], ff: F[A => B]): IsEq[F[B]] = { def selectM[G[_]: Monad](gab: G[Either[A, B]])(gf: G[A => B]) = gab.flatMap { - case Left(a) => gf.map(_(a)) + case Left(a) => gf.map(_(a)) case Right(b) => b.pure[G] } F.select(fab)(ff) <-> selectM(fab)(ff) From 955a03b7936c4206677f7bd6db2ebafd693daf96 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 10 Dec 2020 01:47:31 -0500 Subject: [PATCH 15/46] Use our new syntax --- core/src/main/scala/cats/syntax/all.scala | 1 + .../main/scala/cats/syntax/selective.scala | 24 +++++++++++++++++++ laws/src/main/scala/cats/laws/MonadLaws.scala | 2 +- .../main/scala/cats/laws/SelectiveLaws.scala | 6 ++--- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 core/src/main/scala/cats/syntax/selective.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index f9e6e2fa39..98ba5eac96 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -51,6 +51,7 @@ trait AllSyntax with PartialOrderSyntax with ProfunctorSyntax with ReducibleSyntax + with SelectiveSyntax with SemigroupSyntax with SemigroupKSyntax with ShowSyntax diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala new file mode 100644 index 0000000000..f42091e52f --- /dev/null +++ b/core/src/main/scala/cats/syntax/selective.scala @@ -0,0 +1,24 @@ +package cats +package syntax + +trait SelectiveSyntax { + implicit final def catsSyntaxSelective[F[_], A, B](fab: F[Either[A, B]]): SelectiveOps[F, A, B] = + new SelectiveOps(fab) + implicit final def catsSyntaxIfS[F[_]](fBool: F[Boolean]): IfSOps[F] = + new IfSOps(fBool) + implicit final def catsSyntaxWhenS[F[_]](fBool: F[Boolean]): WhenSOps[F] = + new WhenSOps(fBool) +} + +final class SelectiveOps[F[_], A, B](private val fab: F[Either[A, B]]) extends AnyVal { + def select(ff: F[A => B])(implicit F: Selective[F]): F[B] = F.select(fab)(ff) + def branch[C](l: F[A => C])(r: F[B => C])(implicit F: Selective[F]): F[C] = F.branch(fab)(l)(r) +} + +final class IfSOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { + def ifS[A](ifTrue: => F[A], ifFalse: => F[A])(implicit F: Selective[F]): F[A] = F.ifS(fBool)(ifTrue)(ifFalse) +} + +final class WhenSOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { + def whenS[A](fa: F[Unit])(implicit F: Selective[F]): F[Unit] = F.whenS(fBool)(fa) +} diff --git a/laws/src/main/scala/cats/laws/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala index 02b6d467bb..f37d8bb960 100644 --- a/laws/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadLaws.scala @@ -48,7 +48,7 @@ trait MonadLaws[F[_]] extends SelectiveLaws[F] with FlatMapLaws[F] { case Left(a) => gf.map(_(a)) case Right(b) => b.pure[G] } - F.select(fab)(ff) <-> selectM(fab)(ff) + fab.select(ff) <-> selectM(fab)(ff) } } diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index c7882de228..66c5b3f5d1 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -1,7 +1,7 @@ package cats package laws -import cats.syntax.functor._ +import cats.syntax.all._ /** * Laws that must be obeyed by any `Selective`. @@ -10,10 +10,10 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { implicit override def F: Selective[F] def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = - F.select(faa)(F.pure(identity)) <-> faa.map(_.merge) + faa.select(F.pure(identity)) <-> faa.map(_.merge) def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = - F.select(F.pure(ab))(F.productR(ff1)(ff2)) <-> F.productR(F.select(F.pure(ab))(ff1))(F.select(F.pure(ab))(ff2)) + F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) // TODO associativity } From e35a097bfa8695156ea8d341e0cf823f52f434fe Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 10 Dec 2020 02:07:51 -0500 Subject: [PATCH 16/46] Test default operations --- core/src/main/scala/cats/Selective.scala | 8 +++---- .../main/scala/cats/syntax/selective.scala | 2 +- .../main/scala/cats/laws/SelectiveLaws.scala | 24 +++++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index bd50517d05..ab57d2e329 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -7,13 +7,13 @@ import scala.annotation.implicitNotFound @typeclass trait Selective[F[_]] extends Applicative[F] { def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] - def branch[A, B, C](x: F[Either[A, B]])(l: F[A => C])(r: F[B => C]): F[C] = { + def branch[A, B, C](fab: F[Either[A, B]])(fl: F[A => C])(fr: F[B => C]): F[C] = { val lhs = { - val innerLhs: F[Either[A, Either[B, C]]] = map(x)(_.map(Left(_))) - val innerRhs: F[A => Either[B, C]] = map(l)(_.andThen(Right(_))) + val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) + val innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) select(innerLhs)(innerRhs) } - select(lhs)(r) + select(lhs)(fr) } def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala index f42091e52f..abfda28363 100644 --- a/core/src/main/scala/cats/syntax/selective.scala +++ b/core/src/main/scala/cats/syntax/selective.scala @@ -16,7 +16,7 @@ final class SelectiveOps[F[_], A, B](private val fab: F[Either[A, B]]) extends A } final class IfSOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { - def ifS[A](ifTrue: => F[A], ifFalse: => F[A])(implicit F: Selective[F]): F[A] = F.ifS(fBool)(ifTrue)(ifFalse) + def ifS[A](ifTrue: => F[A])(ifFalse: => F[A])(implicit F: Selective[F]): F[A] = F.ifS(fBool)(ifTrue)(ifFalse) } final class WhenSOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index 66c5b3f5d1..6ab087ec2d 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -16,6 +16,30 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) // TODO associativity + + def selectiveBranchConsistency[A, B, C](fab: F[Either[A, B]], fl: F[A => C], fr: F[B => C]): IsEq[F[C]] = { + fab.branch(fl)(fr) <-> { + val lhs = { + val innerLhs: F[Either[A, Either[B, C]]] = F.map(fab)(_.map(Left(_))) + val innerRhs: F[A => Either[B, C]] = F.map(fl)(_.andThen(Right(_))) + F.select(innerLhs)(innerRhs) + } + F.select(lhs)(fr) + } + } + + def selectiveIfSConsistency[A](fb: F[Boolean], ft: F[A], ff: F[A]): IsEq[F[A]] = { + fb.ifS(ft)(ff) <-> { + val condition: F[Either[Unit, Unit]] = F.map(fb)(p => if (p) Left(()) else Right(())) + val left: F[Unit => A] = F.map(ft)(Function.const) + val right: F[Unit => A] = F.map(ff)(Function.const) + F.branch(condition)(left)(right) + } + } + + def selectiveWhenSConsistency[A](fb: F[Boolean], fa: F[Unit]): IsEq[F[Unit]] = { + fb.whenS(fa) <-> F.ifS(fb)(fa)(F.unit) + } } object SelectiveLaws { From df699d160b5f5f4aedc1eee3fc38f5f51567387f Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 13 Dec 2020 22:09:15 -0500 Subject: [PATCH 17/46] Derive missing implicits for selective laws --- .../cats/laws/discipline/MonadTests.scala | 22 ++-------- .../cats/laws/discipline/SelectiveTests.scala | 40 +++++++++++++++++-- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index a1a813a136..d2c00cfaf7 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -4,8 +4,7 @@ package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.platform.Platform -import cats.syntax.all._ -import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} +import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { @@ -26,14 +25,7 @@ trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { EqFABC: Eq[F[(A, B, C)]], EqFInt: Eq[F[Int]], iso: Isomorphisms[F] - ): RuleSet = { - implicit def ArbFAA: Arbitrary[F[Either[A, A]]] = - Arbitrary( - Gen.oneOf(ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) - ) - ) - + ): RuleSet = new RuleSet { def name: String = "monad" def bases: Seq[(String, RuleSet)] = Nil @@ -46,7 +38,6 @@ trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { ) ++ (if (Platform.isJvm) Seq[(String, Prop)]("tailRecM stack safety" -> Prop.lzy(laws.tailRecMStackSafety)) else Seq.empty) } - } def stackUnsafeMonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit ArbFA: Arbitrary[F[A]], @@ -63,13 +54,7 @@ trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { EqFABC: Eq[F[(A, B, C)]], EqFInt: Eq[F[Int]], iso: Isomorphisms[F] - ): RuleSet = { - implicit def ArbFAA: Arbitrary[F[Either[A, A]]] = - Arbitrary( - Gen.oneOf(ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) - ) - ) + ): RuleSet = new RuleSet { def name: String = "monad (stack-unsafe)" def bases: Seq[(String, RuleSet)] = Nil @@ -81,7 +66,6 @@ trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { "map flatMap coherence" -> forAll(laws.mapFlatMapCoherence[A, B] _) ) } - } } object MonadTests { diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index 798c7f9890..ce80906ed9 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -3,7 +3,8 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import org.scalacheck.{Arbitrary, Cogen, Prop} +import cats.syntax.all._ +import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} import Prop._ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { @@ -15,7 +16,6 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { ArbFC: Arbitrary[F[C]], ArbFAtoB: Arbitrary[F[A => B]], ArbFBtoC: Arbitrary[F[B => C]], - ArbFAA: Arbitrary[F[Either[A, A]]], CogenA: Cogen[A], CogenB: Cogen[B], CogenC: Cogen[C], @@ -26,16 +26,50 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { EqFInt: Eq[F[Int]], iso: Isomorphisms[F] ): RuleSet = { + implicit val ArbFBool: Arbitrary[F[Boolean]] = arbFB[A, Boolean] + implicit val ArbFUnit: Arbitrary[F[Unit]] = arbFB[A, Unit] new RuleSet { def name: String = "selective" def bases: Seq[(String, RuleSet)] = Nil def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "selective identity" -> forAll(laws.selectiveIdentity[A, B] _) + "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), + "selective branch consistency" -> forAll(laws.selectiveBranchConsistency[A, B, C] _), + "selective ifS consistency" -> forAll(laws.selectiveIfSConsistency[A] _), + "selective whenS consistency" -> forAll(laws.selectiveWhenSConsistency[A] _) ) } } + + implicit private def arbFAB[A, B](implicit + arbFA: Arbitrary[F[A]], + arbFB: Arbitrary[F[B]] + ): Arbitrary[F[Either[A, B]]] = { + Arbitrary( + Gen.oneOf(arbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), + arbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) + ) + ) + } + + private def arbFB[A, B](implicit arbFA: Arbitrary[F[A]], arbB: Arbitrary[B]): Arbitrary[F[B]] = + Arbitrary(for { + fa <- arbFA.arbitrary + b <- arbB.arbitrary + } yield laws.F.as(fa, b)) + + implicit private def arbFAtoC[A, B, C](implicit + arbFAtoB: Arbitrary[F[A => B]], + arbFBtoC: Arbitrary[F[B => C]] + ): Arbitrary[F[A => C]] = + Arbitrary(for { + fAToB <- arbFAtoB.arbitrary + fBToC <- arbFBtoC.arbitrary + } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) + + implicit private def eqFUnit(implicit eqFInt: Eq[F[Int]]): Eq[F[Unit]] = + Eq.by(laws.F.map(_)(_ => 0)) } object SelectiveTests { From a50da480ce2967a6e9d23a722dd7df09b27211c0 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 13 Dec 2020 22:51:01 -0500 Subject: [PATCH 18/46] Remove SelectiveError for now --- core/src/main/scala/cats/MonadError.scala | 8 +++++++- core/src/main/scala/cats/SelectiveError.scala | 15 --------------- core/src/main/scala/cats/data/Validated.scala | 6 ++++-- 3 files changed, 11 insertions(+), 18 deletions(-) delete mode 100644 core/src/main/scala/cats/SelectiveError.scala diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 90e456d570..97e8264250 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -5,7 +5,13 @@ package cats * * This type class allows one to abstract over error-handling monads. */ -trait MonadError[F[_], E] extends SelectiveError[F, E] with Monad[F] { +trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { + + /** + * Turns a successful value into an error if it does not satisfy a given predicate. + */ + def ensure[A](fa: F[A])(error: => E)(predicate: A => Boolean): F[A] = + flatMap(fa)(a => if (predicate(a)) pure(a) else raiseError(error)) /** * Turns a successful value into an error specified by the `error` function if it does not satisfy a given predicate. diff --git a/core/src/main/scala/cats/SelectiveError.scala b/core/src/main/scala/cats/SelectiveError.scala deleted file mode 100644 index 61a963c890..0000000000 --- a/core/src/main/scala/cats/SelectiveError.scala +++ /dev/null @@ -1,15 +0,0 @@ -package cats - -import cats.syntax.either._ - -trait SelectiveError[F[_], E] extends ApplicativeError[F, E] with Selective[F] { - - /** - * Turns a successful value into an error if it does not satisfy a given predicate. - */ - def ensure[A](fa: F[A])(error: => E)(predicate: A => Boolean): F[A] = - select(map(fa) { (a: A) => - if (predicate(a)) Right(a) - else Either.leftUnit - })(raiseError(error)) -} diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 64b7411435..5a8764e796 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -931,8 +931,10 @@ sealed abstract private[data] class ValidatedInstances extends ValidatedInstance fab.leftMap(f) } - implicit def catsDataSelectiveErrorForValidated[E](implicit E: Semigroup[E]): SelectiveError[Validated[E, *], E] = - new ValidatedSelective[E] with SelectiveError[Validated[E, *], E] { + implicit def catsDataSelectiveErrorForValidated[E](implicit + E: Semigroup[E] + ): Selective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = + new ValidatedSelective[E] with ApplicativeError[Validated[E, *], E] { def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = fa match { case Validated.Invalid(e) => f(e) From 387ab75879f778c779bd423ca49381f1aae8583f Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Mon, 14 Dec 2020 13:47:38 -0500 Subject: [PATCH 19/46] Selective distributivity and associativity --- core/src/main/scala/cats/Selective.scala | 4 ++++ laws/src/main/scala/cats/laws/SelectiveLaws.scala | 7 ++++++- .../scala/cats/laws/discipline/SelectiveTests.scala | 13 +++++++++++++ .../src/test/scala/cats/tests/ValidatedSuite.scala | 4 ++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index ab57d2e329..e062c87d99 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -26,3 +26,7 @@ import scala.annotation.implicitNotFound def whenS[A](fbool: F[Boolean])(fa: F[Unit]): F[Unit] = ifS(fbool)(fa)(unit) } + +object Selective { + @inline def apply[F[_]](implicit instance: Selective[F]): Selective[F] = instance +} diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index 6ab087ec2d..c7666e7732 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -15,7 +15,12 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) - // TODO associativity + def selectiveAssociativity[A, B, C](fa: F[Either[A, B]], fb: F[Either[C, A => B]], fc: F[C => A => B]): IsEq[F[B]] = { + val fa0 = fa.map(_.map(_.asRight[(C, A)])) + val fb0 = fb.map { either => (a: A) => either.bimap(c => (c, a), f => f(a)) } + val fc0 = fc.map(Function.uncurried(_).tupled) + fa.select(fb.select(fc)) <-> fa0.select(fb0).select(fc0) + } def selectiveBranchConsistency[A, B, C](fab: F[Either[A, B]], fl: F[A => C], fr: F[B => C]): IsEq[F[C]] = { fab.branch(fl)(fr) <-> { diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index ce80906ed9..94020044c5 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -35,6 +35,8 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { def props: Seq[(String, Prop)] = Seq( "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), + "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _), + "selective associativity" -> forAll(laws.selectiveAssociativity[A, B, C] _), "selective branch consistency" -> forAll(laws.selectiveBranchConsistency[A, B, C] _), "selective ifS consistency" -> forAll(laws.selectiveIfSConsistency[A] _), "selective whenS consistency" -> forAll(laws.selectiveWhenSConsistency[A] _) @@ -68,6 +70,17 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { fBToC <- arbFBtoC.arbitrary } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) + implicit private def arbFAtoBtoC[A, B, C](implicit + arbFA: Arbitrary[F[A]], + arbC: Arbitrary[C], + cogenA: Cogen[A], + cogenB: Cogen[B] + ): Arbitrary[F[A => B => C]] = + Arbitrary(for { + fa <- arbFA.arbitrary + f <- Gen.function1(Gen.function1(arbC.arbitrary)(cogenB))(cogenA) + } yield laws.F.as(fa, f)) + implicit private def eqFUnit(implicit eqFInt: Eq[F[Int]]): Eq[F[Unit]] = Eq.by(laws.F.map(_)(_ => 0)) } diff --git a/tests/src/test/scala/cats/tests/ValidatedSuite.scala b/tests/src/test/scala/cats/tests/ValidatedSuite.scala index 281afc9c0a..754c8fb7a0 100644 --- a/tests/src/test/scala/cats/tests/ValidatedSuite.scala +++ b/tests/src/test/scala/cats/tests/ValidatedSuite.scala @@ -6,6 +6,7 @@ import cats.{ ApplicativeError, Bitraverse, CommutativeApplicative, + Selective, SemigroupK, Semigroupal, Show, @@ -45,6 +46,9 @@ class ValidatedSuite extends CatsSuite { SerializableTests.serializable(ApplicativeError[Validated[String, *], String]) ) + checkAll("Validated[String, Int]", SelectiveTests[Validated[String, *]].selective[Int, Int, Int]) + checkAll("Selective[Validated[String, *]]", SerializableTests.serializable(Selective[Validated[String, *]])) + checkAll("Validated[String, Int] with Option", TraverseTests[Validated[String, *]].traverse[Int, Int, Int, Int, Option, Option] ) From cb515021ed0e7ce8fc949a22ceea4210d8039fdc Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Mon, 14 Dec 2020 15:13:53 -0500 Subject: [PATCH 20/46] Clean up syntax --- core/src/main/scala/cats/Selective.scala | 50 ++++++++++++++++++- .../main/scala/cats/syntax/selective.scala | 20 ++------ .../main/scala/cats/laws/SelectiveLaws.scala | 2 +- 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index e062c87d99..1df8ed02c8 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -1,6 +1,6 @@ package cats -import simulacrum.{typeclass} +import simulacrum.{noop, typeclass} import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") @@ -16,6 +16,7 @@ import scala.annotation.implicitNotFound select(lhs)(fr) } + @noop def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) Left(()) else Right(())) val left: F[Unit => A] = map(t)(Function.const) @@ -23,10 +24,57 @@ import scala.annotation.implicitNotFound branch(condition)(left)(right) } + @noop def whenS[A](fbool: F[Boolean])(fa: F[Unit]): F[Unit] = ifS(fbool)(fa)(unit) } object Selective { + /* ======================================================================== */ + /* THE FOLLOWING CODE IS MANAGED BY SIMULACRUM; PLEASE DO NOT EDIT!!!! */ + /* ======================================================================== */ + + /** + * Summon an instance of [[Selective]] for `F`. + */ @inline def apply[F[_]](implicit instance: Selective[F]): Selective[F] = instance + + @deprecated("Use cats.syntax object imports", "2.2.0") + object ops { + implicit def toAllSelectiveOps[F[_], A](target: F[A])(implicit tc: Selective[F]): AllOps[F, A] { + type TypeClassType = Selective[F] + } = new AllOps[F, A] { + type TypeClassType = Selective[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } + } + trait Ops[F[_], A] extends Serializable { + type TypeClassType <: Selective[F] + def self: F[A] + val typeClassInstance: TypeClassType + def select[B, C](ff: F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = + typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) + def branch[B, C, D](fl: F[B => D])(fr: F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = + typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) + } + trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { + type TypeClassType <: Selective[F] + } + trait ToSelectiveOps extends Serializable { + implicit def toSelectiveOps[F[_], A](target: F[A])(implicit tc: Selective[F]): Ops[F, A] { + type TypeClassType = Selective[F] + } = new Ops[F, A] { + type TypeClassType = Selective[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } + } + @deprecated("Use cats.syntax object imports", "2.2.0") + object nonInheritedOps extends ToSelectiveOps + + /* ======================================================================== */ + /* END OF SIMULACRUM-MANAGED CODE */ + /* ======================================================================== */ + } diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala index abfda28363..45ffc07c6a 100644 --- a/core/src/main/scala/cats/syntax/selective.scala +++ b/core/src/main/scala/cats/syntax/selective.scala @@ -1,24 +1,12 @@ package cats package syntax -trait SelectiveSyntax { - implicit final def catsSyntaxSelective[F[_], A, B](fab: F[Either[A, B]]): SelectiveOps[F, A, B] = - new SelectiveOps(fab) - implicit final def catsSyntaxIfS[F[_]](fBool: F[Boolean]): IfSOps[F] = - new IfSOps(fBool) - implicit final def catsSyntaxWhenS[F[_]](fBool: F[Boolean]): WhenSOps[F] = - new WhenSOps(fBool) +trait SelectiveSyntax extends Selective.ToSelectiveOps { + implicit final def catsSyntaxSelectiveBooleanOps[F[_]](fBool: F[Boolean]): SelectiveBooleanOps[F] = + new SelectiveBooleanOps(fBool) } -final class SelectiveOps[F[_], A, B](private val fab: F[Either[A, B]]) extends AnyVal { - def select(ff: F[A => B])(implicit F: Selective[F]): F[B] = F.select(fab)(ff) - def branch[C](l: F[A => C])(r: F[B => C])(implicit F: Selective[F]): F[C] = F.branch(fab)(l)(r) -} - -final class IfSOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { +final class SelectiveBooleanOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { def ifS[A](ifTrue: => F[A])(ifFalse: => F[A])(implicit F: Selective[F]): F[A] = F.ifS(fBool)(ifTrue)(ifFalse) -} - -final class WhenSOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { def whenS[A](fa: F[Unit])(implicit F: Selective[F]): F[Unit] = F.whenS(fBool)(fa) } diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index c7666e7732..10425c9381 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -10,7 +10,7 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { implicit override def F: Selective[F] def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = - faa.select(F.pure(identity)) <-> faa.map(_.merge) + faa.select[A, A](F.pure(identity)) <-> faa.map(_.merge) def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) From 06abe62881eba8b7710cfbef986728f42c6d0466 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Tue, 15 Dec 2020 19:44:36 -0500 Subject: [PATCH 21/46] apS and RigidSelective laws --- core/src/main/scala/cats/Selective.scala | 8 ++++ laws/src/main/scala/cats/laws/MonadLaws.scala | 2 +- .../scala/cats/laws/RigidSelectiveLaws.scala | 19 ++++++++ .../cats/laws/discipline/MonadTests.scala | 2 +- .../laws/discipline/RigidSelectiveTests.scala | 45 +++++++++++++++++++ 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 1df8ed02c8..53d6d0f96e 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -16,6 +16,12 @@ import scala.annotation.implicitNotFound select(lhs)(fr) } + def apS[A, B](ff: F[A => B])(fa: F[A]): F[B] = { + val left: F[Either[A => B, B]] = map(ff)(Left(_)) + val right: F[(A => B) => B] = map(fa)((a: A) => _(a)) + select(left)(right) + } + @noop def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) Left(()) else Right(())) @@ -57,6 +63,8 @@ object Selective { typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) def branch[B, C, D](fl: F[B => D])(fr: F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) + def apS[B, C](fa: F[B])(implicit ev$1: A <:< (B => C)): F[C] = + typeClassInstance.apS[B, C](self.asInstanceOf[F[B => C]])(fa) } trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { type TypeClassType <: Selective[F] diff --git a/laws/src/main/scala/cats/laws/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala index f37d8bb960..f808d8945c 100644 --- a/laws/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadLaws.scala @@ -7,7 +7,7 @@ import cats.implicits._ /** * Laws that must be obeyed by any `Monad`. */ -trait MonadLaws[F[_]] extends SelectiveLaws[F] with FlatMapLaws[F] { +trait MonadLaws[F[_]] extends RigidSelectiveLaws[F] with FlatMapLaws[F] { implicit override def F: Monad[F] def monadLeftIdentity[A, B](a: A, f: A => F[B]): IsEq[F[B]] = diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala new file mode 100644 index 0000000000..939fe2be37 --- /dev/null +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -0,0 +1,19 @@ +package cats +package laws + +import cats.syntax.all._ + +/** + * Laws that must be obeyed by any rigid `Selective`. + */ +trait RigidSelectiveLaws[F[_]] extends ApplicativeLaws[F] { + implicit override def F: Selective[F] + + def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = + ff.apS(fa) <-> ff.ap(fa) +} + +object RigidSelectiveLaws { + def apply[F[_]](implicit ev: Selective[F]): RigidSelectiveLaws[F] = + new RigidSelectiveLaws[F] { def F: Selective[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index d2c00cfaf7..9897162580 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -7,7 +7,7 @@ import cats.platform.Platform import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait MonadTests[F[_]] extends SelectiveTests[F] with FlatMapTests[F] { +trait MonadTests[F[_]] extends RigidSelectiveTests[F] with FlatMapTests[F] { def laws: MonadLaws[F] def monad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala new file mode 100644 index 0000000000..30fa4518e8 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -0,0 +1,45 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.SemigroupalTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait RigidSelectiveTests[F[_]] extends ApplicativeTests[F] { + def laws: RigidSelectiveLaws[F] + + def selective[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 = "rigidSelective" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(selective[A, B, C]) + def props: Seq[(String, Prop)] = + Seq( + "selective apply" -> forAll(laws.selectiveApply[A, B] _) + ) + } + } +} + +object RigidSelectiveTests { + def apply[F[_]: Selective]: RigidSelectiveTests[F] = + new RigidSelectiveTests[F] { + def laws: RigidSelectiveLaws[F] = RigidSelectiveLaws[F] + } +} From 9b001dd6c7f1d77b1fd96a2b3b246aa042543f1e Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Tue, 15 Dec 2020 19:45:24 -0500 Subject: [PATCH 22/46] Remove inner scope in branch --- core/src/main/scala/cats/Selective.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 53d6d0f96e..98fb62b390 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -8,11 +8,9 @@ import scala.annotation.implicitNotFound def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] def branch[A, B, C](fab: F[Either[A, B]])(fl: F[A => C])(fr: F[B => C]): F[C] = { - val lhs = { - val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) - val innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) - select(innerLhs)(innerRhs) - } + val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) + val innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) + val lhs = select(innerLhs)(innerRhs) select(lhs)(fr) } From 19b67b4422c0374aec443fcfebe7188bd6e13599 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Tue, 15 Dec 2020 20:14:56 -0500 Subject: [PATCH 23/46] Move EitherUtil to root package for use in typeclasses --- core/src/main/scala/cats/EitherUtil.scala | 14 +++++++++++ core/src/main/scala/cats/data/EitherT.scala | 2 +- .../main/scala/cats/instances/either.scala | 1 - core/src/main/scala/cats/syntax/either.scala | 25 ++++++++++--------- 4 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 core/src/main/scala/cats/EitherUtil.scala diff --git a/core/src/main/scala/cats/EitherUtil.scala b/core/src/main/scala/cats/EitherUtil.scala new file mode 100644 index 0000000000..a9f10d5cf2 --- /dev/null +++ b/core/src/main/scala/cats/EitherUtil.scala @@ -0,0 +1,14 @@ +package cats + +/** + * Convenience methods and values for Either. + */ +private[cats] object EitherUtil { + def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = + right.asInstanceOf[Either[C, B]] + def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = + left.asInstanceOf[Either[A, C]] + + private[cats] val unit = Right(()) + private[cats] val leftUnit = Left(()) +} diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 6dc7ba70be..4394825d57 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -2,7 +2,7 @@ package cats package data import cats.Bifunctor -import cats.syntax.EitherUtil +import cats.EitherUtil /** * Transformer for `Either`, allowing the effect of an arbitrary type constructor `F` to be combined with the diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 9830342235..a82436b88e 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -3,7 +3,6 @@ package instances import cats.data.Validated import cats.kernel.Semigroup -import cats.syntax.EitherUtil import cats.syntax.either._ import scala.annotation.tailrec diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index f13bf5d788..b9e81dd1d4 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -54,7 +54,7 @@ final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal { def orElse[C, BB >: B](fallback: => Either[C, BB]): Either[C, BB] = eab match { case Left(_) => fallback - case r @ Right(_) => EitherUtil.leftCast(r) + case r @ Right(_) => cats.EitherUtil.leftCast(r) } def recover[BB >: B](pf: PartialFunction[A, BB]): Either[A, BB] = @@ -157,20 +157,20 @@ final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal { @deprecated("Included in the standard library", "2.1.0-RC1") private[syntax] def map[C](f: B => C): Either[A, C] = eab match { - case l @ Left(_) => EitherUtil.rightCast(l) + case l @ Left(_) => cats.EitherUtil.rightCast(l) case Right(b) => Right(f(b)) } def map2Eval[AA >: A, C, Z](fc: Eval[Either[AA, C]])(f: (B, C) => Z): Eval[Either[AA, Z]] = eab match { - case l @ Left(_) => Now(EitherUtil.rightCast(l)) + case l @ Left(_) => Now(cats.EitherUtil.rightCast(l)) case Right(b) => fc.map(_.map(f(b, _))) } def leftMap[C](f: A => C): Either[C, B] = eab match { case Left(a) => Left(f(a)) - case r @ Right(_) => EitherUtil.leftCast(r) + case r @ Right(_) => cats.EitherUtil.leftCast(r) } @deprecated("Included in the standard library", "2.1.0-RC1") @@ -183,7 +183,7 @@ final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal { def leftFlatMap[C, BB >: B](f: A => Either[C, BB]): Either[C, BB] = eab match { case Left(a) => f(a) - case r @ Right(_) => EitherUtil.leftCast(r) + case r @ Right(_) => cats.EitherUtil.leftCast(r) } def compare[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: Order[AA], BB: Order[BB]): Int = @@ -232,7 +232,7 @@ final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal { def traverse[F[_], AA >: A, D](f: B => F[D])(implicit F: Applicative[F]): F[Either[AA, D]] = eab match { - case l @ Left(_) => F.pure(EitherUtil.rightCast(l)) + case l @ Left(_) => F.pure(cats.EitherUtil.rightCast(l)) case Right(b) => F.map(f(b))(Right(_)) } @@ -394,12 +394,12 @@ final class EitherObjectOps(private val either: Either.type) extends AnyVal { // /** * Cached value of `Right(())` to avoid allocations for a common case. */ - def unit[A]: Either[A, Unit] = EitherUtil.unit + def unit[A]: Either[A, Unit] = cats.EitherUtil.unit /** * Cached value of `Left(())` to avoid allocations for a common case. */ - def leftUnit[B]: Either[Unit, B] = EitherUtil.leftUnit + def leftUnit[B]: Either[Unit, B] = cats.EitherUtil.leftUnit } final class LeftOps[A, B](private val left: Left[A, B]) extends AnyVal { @@ -507,12 +507,13 @@ final private[syntax] class EitherOpsBinCompat0[A, B](private val value: Either[ /** * Convenience methods to use `Either` syntax inside `Either` syntax definitions. */ +@deprecated("Moved to cats.EitherUtil", "2.4.0") private[cats] object EitherUtil { def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = - right.asInstanceOf[Either[C, B]] + cats.EitherUtil.leftCast(right) def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = - left.asInstanceOf[Either[A, C]] + cats.EitherUtil.rightCast(left) - private[cats] val unit = Right(()) - private[cats] val leftUnit = Left(()) + private[cats] def unit = cats.EitherUtil.unit + private[cats] def leftUnit = cats.EitherUtil.leftUnit } From 662cf5a68ca4cf32ecc7e711ab1f934ddbece086 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Tue, 15 Dec 2020 20:17:24 -0500 Subject: [PATCH 24/46] Use cached either units in ifS --- core/src/main/scala/cats/Selective.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 98fb62b390..6bba86b07a 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -22,7 +22,7 @@ import scala.annotation.implicitNotFound @noop def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { - val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) Left(()) else Right(())) + val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) EitherUtil.leftUnit else EitherUtil.unit) val left: F[Unit => A] = map(t)(Function.const) val right: F[Unit => A] = map(e)(Function.const) branch(condition)(left)(right) From 936847f94e5de6457c90257bb4a4bda07d99897c Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Tue, 15 Dec 2020 20:35:13 -0500 Subject: [PATCH 25/46] Fix loop in laws inheritance --- laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala | 2 +- laws/src/main/scala/cats/laws/discipline/MonadTests.scala | 4 ++-- .../main/scala/cats/laws/discipline/RigidSelectiveTests.scala | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index 939fe2be37..19385d00c7 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -6,7 +6,7 @@ import cats.syntax.all._ /** * Laws that must be obeyed by any rigid `Selective`. */ -trait RigidSelectiveLaws[F[_]] extends ApplicativeLaws[F] { +trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { implicit override def F: Selective[F] def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index 9897162580..bb28a3c4f3 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -29,7 +29,7 @@ trait MonadTests[F[_]] extends RigidSelectiveTests[F] with FlatMapTests[F] { new RuleSet { def name: String = "monad" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(selective[A, B, C], flatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(rigidSelective[A, B, C], flatMap[A, B, C]) def props: Seq[(String, Prop)] = Seq( "monad left identity" -> forAll(laws.monadLeftIdentity[A, B] _), @@ -58,7 +58,7 @@ trait MonadTests[F[_]] extends RigidSelectiveTests[F] with FlatMapTests[F] { new RuleSet { def name: String = "monad (stack-unsafe)" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(selective[A, B, C], flatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(rigidSelective[A, B, C], flatMap[A, B, C]) def props: Seq[(String, Prop)] = Seq( "monad left identity" -> forAll(laws.monadLeftIdentity[A, B] _), diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index 30fa4518e8..381abea440 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -6,10 +6,10 @@ import cats.laws.discipline.SemigroupalTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait RigidSelectiveTests[F[_]] extends ApplicativeTests[F] { +trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { def laws: RigidSelectiveLaws[F] - def selective[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + def rigidSelective[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit ArbFA: Arbitrary[F[A]], ArbFB: Arbitrary[F[B]], ArbFC: Arbitrary[F[C]], From 3372948a7f88b38e1e413657b237f26712030654 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Tue, 15 Dec 2020 22:38:37 -0500 Subject: [PATCH 26/46] Remove unnecessary private[cats] in EitherUtil --- core/src/main/scala/cats/EitherUtil.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/EitherUtil.scala b/core/src/main/scala/cats/EitherUtil.scala index a9f10d5cf2..8e2a28bfdd 100644 --- a/core/src/main/scala/cats/EitherUtil.scala +++ b/core/src/main/scala/cats/EitherUtil.scala @@ -9,6 +9,6 @@ private[cats] object EitherUtil { def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = left.asInstanceOf[Either[A, C]] - private[cats] val unit = Right(()) - private[cats] val leftUnit = Left(()) + val unit = Right(()) + val leftUnit = Left(()) } From a866a20c34e2a3bfb9a4d54a01c23177aaa7d617 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Tue, 15 Dec 2020 23:57:05 -0500 Subject: [PATCH 27/46] Apply.selectA --- core/src/main/scala/cats/Apply.scala | 30 +++++++++++-------- laws/src/main/scala/cats/laws/ApplyLaws.scala | 6 ++++ .../cats/laws/discipline/ApplyTests.scala | 27 +++++++++++++++-- .../cats/laws/discipline/SelectiveTests.scala | 21 ------------- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index fb64a1b93f..ec1898a7f7 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -257,6 +257,12 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse ap2(map(fcond)(ite))(ifTrue, ifFalse) } + + def selectA[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = + map2(fab, ff) { + case (Left(a), f) => f(a) + case (Right(b), _) => b + } } object Apply { @@ -289,12 +295,11 @@ object Apply { object ops { implicit def toAllApplyOps[F[_], A](target: F[A])(implicit tc: Apply[F]): AllOps[F, A] { type TypeClassType = Apply[F] - } = - new AllOps[F, A] { - type TypeClassType = Apply[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new AllOps[F, A] { + type TypeClassType = Apply[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } trait Ops[F[_], A] extends Serializable { type TypeClassType <: Apply[F] @@ -312,6 +317,8 @@ object Apply { typeClassInstance.ap2[B, C, D](self.asInstanceOf[F[(B, C) => D]])(fa, fb) def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] = typeClassInstance.map2[A, B, C](self, fb)(f) def map2Eval[B, C](fb: Eval[F[B]])(f: (A, B) => C): Eval[F[C]] = typeClassInstance.map2Eval[A, B, C](self, fb)(f) + def selectA[B, C](ff: F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = + typeClassInstance.selectA[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) } trait AllOps[F[_], A] extends Ops[F, A] with Functor.AllOps[F, A] with InvariantSemigroupal.AllOps[F, A] { type TypeClassType <: Apply[F] @@ -319,12 +326,11 @@ object Apply { trait ToApplyOps extends Serializable { implicit def toApplyOps[F[_], A](target: F[A])(implicit tc: Apply[F]): Ops[F, A] { type TypeClassType = Apply[F] - } = - new Ops[F, A] { - type TypeClassType = Apply[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new Ops[F, A] { + type TypeClassType = Apply[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } @deprecated("Use cats.syntax object imports", "2.2.0") object nonInheritedOps extends ToApplyOps diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index 0d96699131..5aa1338c0a 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -26,6 +26,12 @@ trait ApplyLaws[F[_]] extends FunctorLaws[F] with SemigroupalLaws[F] { def productLConsistency[A, B](fa: F[A], fb: F[B]): IsEq[F[A]] = F.productL(fa)(fb) <-> F.map2(fa, fb)((a, _) => a) + + def selectAConsistency[A, B](fab: F[Either[A, B]], ff: F[A => B]): IsEq[F[B]] = + F.selectA(fab)(ff) <-> F.map2(fab, ff) { + case (Left(a), f) => f(a) + case (Right(b), _) => b + } } object ApplyLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index 57ba7983ab..952b40ac45 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -3,7 +3,8 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import org.scalacheck.{Arbitrary, Cogen, Prop} +import cats.syntax.all._ +import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} import Prop._ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { @@ -32,9 +33,31 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { "map2/product-map consistency" -> forAll(laws.map2ProductConsistency[A, B, C] _), "map2/map2Eval consistency" -> forAll(laws.map2EvalConsistency[A, B, C] _), "productR consistent map2" -> forAll(laws.productRConsistency[A, C] _), - "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _) + "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _), + "selectA consistent map2" -> forAll(laws.selectAConsistency[A, C] _) ) } + + // Derived implicits to preserve bincompat + implicit protected def derivedArbitraryEither[A, B](implicit + arbFA: Arbitrary[F[A]], + arbFB: Arbitrary[F[B]] + ): Arbitrary[F[Either[A, B]]] = { + Arbitrary( + Gen.oneOf(arbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), + arbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) + ) + ) + } + + implicit protected def derivedArbitraryFunctionComposition[A, B, C](implicit + arbFAtoB: Arbitrary[F[A => B]], + arbFBtoC: Arbitrary[F[B => C]] + ): Arbitrary[F[A => C]] = + Arbitrary(for { + fAToB <- arbFAtoB.arbitrary + fBToC <- arbFBtoC.arbitrary + } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) } object ApplyTests { diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index 94020044c5..5958e10e48 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -3,7 +3,6 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import cats.syntax.all._ import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} import Prop._ @@ -44,32 +43,12 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { } } - implicit private def arbFAB[A, B](implicit - arbFA: Arbitrary[F[A]], - arbFB: Arbitrary[F[B]] - ): Arbitrary[F[Either[A, B]]] = { - Arbitrary( - Gen.oneOf(arbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), - arbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) - ) - ) - } - private def arbFB[A, B](implicit arbFA: Arbitrary[F[A]], arbB: Arbitrary[B]): Arbitrary[F[B]] = Arbitrary(for { fa <- arbFA.arbitrary b <- arbB.arbitrary } yield laws.F.as(fa, b)) - implicit private def arbFAtoC[A, B, C](implicit - arbFAtoB: Arbitrary[F[A => B]], - arbFBtoC: Arbitrary[F[B => C]] - ): Arbitrary[F[A => C]] = - Arbitrary(for { - fAToB <- arbFAtoB.arbitrary - fBToC <- arbFBtoC.arbitrary - } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) - implicit private def arbFAtoBtoC[A, B, C](implicit arbFA: Arbitrary[F[A]], arbC: Arbitrary[C], From 260a219068ff92fa06abdafccefc13f04f2fa4e0 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 16 Dec 2020 01:16:58 -0500 Subject: [PATCH 28/46] select's function is lazy; skip on right law for rigids --- core/src/main/scala/cats/Monad.scala | 26 +++++++++---------- core/src/main/scala/cats/Selective.scala | 4 +-- core/src/main/scala/cats/data/Validated.scala | 2 +- .../scala/cats/laws/RigidSelectiveLaws.scala | 5 ++++ .../laws/discipline/RigidSelectiveTests.scala | 3 ++- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/core/src/main/scala/cats/Monad.scala b/core/src/main/scala/cats/Monad.scala index c952fd5e09..cb697146da 100644 --- a/core/src/main/scala/cats/Monad.scala +++ b/core/src/main/scala/cats/Monad.scala @@ -17,7 +17,7 @@ import scala.annotation.implicitNotFound override def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a))) - override def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = + override def select[A, B](fab: F[Either[A, B]])(ff: => F[A => B]): F[B] = flatMap(fab) { case Left(a) => map(ff)(_(a)) case Right(b) => pure(b) @@ -167,12 +167,11 @@ object Monad { object ops { implicit def toAllMonadOps[F[_], A](target: F[A])(implicit tc: Monad[F]): AllOps[F, A] { type TypeClassType = Monad[F] - } = - new AllOps[F, A] { - type TypeClassType = Monad[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new AllOps[F, A] { + type TypeClassType = Monad[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } trait Ops[F[_], A] extends Serializable { type TypeClassType <: Monad[F] @@ -184,18 +183,17 @@ object Monad { def iterateWhile(p: A => Boolean): F[A] = typeClassInstance.iterateWhile[A](self)(p) def iterateUntil(p: A => Boolean): F[A] = typeClassInstance.iterateUntil[A](self)(p) } - trait AllOps[F[_], A] extends Ops[F, A] with FlatMap.AllOps[F, A] with Applicative.AllOps[F, A] { + trait AllOps[F[_], A] extends Ops[F, A] with FlatMap.AllOps[F, A] with Selective.AllOps[F, A] { type TypeClassType <: Monad[F] } trait ToMonadOps extends Serializable { implicit def toMonadOps[F[_], A](target: F[A])(implicit tc: Monad[F]): Ops[F, A] { type TypeClassType = Monad[F] - } = - new Ops[F, A] { - type TypeClassType = Monad[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new Ops[F, A] { + type TypeClassType = Monad[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } @deprecated("Use cats.syntax object imports", "2.2.0") object nonInheritedOps extends ToMonadOps diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 6bba86b07a..bdc6bafee5 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -5,7 +5,7 @@ import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") @typeclass trait Selective[F[_]] extends Applicative[F] { - def select[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] + def select[A, B](fab: F[Either[A, B]])(ff: => F[A => B]): F[B] def branch[A, B, C](fab: F[Either[A, B]])(fl: F[A => C])(fr: F[B => C]): F[C] = { val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) @@ -57,7 +57,7 @@ object Selective { type TypeClassType <: Selective[F] def self: F[A] val typeClassInstance: TypeClassType - def select[B, C](ff: F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = + def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) def branch[B, C, D](fl: F[B => D])(fr: F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 5a8764e796..fd09899027 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -1045,7 +1045,7 @@ sealed abstract private[data] class ValidatedInstances2 { } private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] with Selective[Validated[E, *]] { - override def select[A, B](fab: Validated[E, Either[A, B]])(ff: Validated[E, A => B]): Validated[E, B] = + override def select[A, B](fab: Validated[E, Either[A, B]])(ff: => Validated[E, A => B]): Validated[E, B] = fab match { case Valid(Left(a)) => ff.map(_(a)) case Valid(Right(b)) => Valid(b) diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index 19385d00c7..5ca4d71167 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -11,6 +11,11 @@ trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = ff.apS(fa) <-> ff.ap(fa) + + def selectiveSkipOnRight[A, B](fb: F[B]): IsEq[F[B]] = { + def ope = F.pure(sys.error("ope"): A => B) + fb.map(b => Right(b)).select(ope) <-> fb + } } object RigidSelectiveLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index 381abea440..aa06b15500 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -31,7 +31,8 @@ trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { def parents: Seq[RuleSet] = Seq(selective[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "selective apply" -> forAll(laws.selectiveApply[A, B] _) + "selective apply" -> forAll(laws.selectiveApply[A, B] _), + "selective skip on right" -> forAll(laws.selectiveSkipOnRight[A, B] _) ) } } From a0f87095646a3a6035e5bc9598a816ab70fe0c30 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 16 Dec 2020 18:09:36 -0500 Subject: [PATCH 29/46] Skip effects in branch and ifS in rigid selectives --- core/src/main/scala/cats/Selective.scala | 24 +++++++++---------- .../scala/cats/laws/RigidSelectiveLaws.scala | 20 ++++++++++++---- .../laws/discipline/RigidSelectiveTests.scala | 6 ++++- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index bdc6bafee5..56978c98ca 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -7,9 +7,9 @@ import scala.annotation.implicitNotFound @typeclass trait Selective[F[_]] extends Applicative[F] { def select[A, B](fab: F[Either[A, B]])(ff: => F[A => B]): F[B] - def branch[A, B, C](fab: F[Either[A, B]])(fl: F[A => C])(fr: F[B => C]): F[C] = { + def branch[A, B, C](fab: F[Either[A, B]])(fl: => F[A => C])(fr: => F[B => C]): F[C] = { val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) - val innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) + def innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) val lhs = select(innerLhs)(innerRhs) select(lhs)(fr) } @@ -21,15 +21,15 @@ import scala.annotation.implicitNotFound } @noop - def ifS[A](x: F[Boolean])(t: F[A])(e: F[A]): F[A] = { - val condition: F[Either[Unit, Unit]] = map(x)(p => if (p) EitherUtil.leftUnit else EitherUtil.unit) - val left: F[Unit => A] = map(t)(Function.const) - val right: F[Unit => A] = map(e)(Function.const) + def ifS[A](fCond: F[Boolean])(fTrue: => F[A])(fFalse: => F[A]): F[A] = { + val condition: F[Either[Unit, Unit]] = map(fCond)(if (_) EitherUtil.leftUnit else EitherUtil.unit) + def left: F[Unit => A] = map(fTrue)(Function.const) + def right: F[Unit => A] = map(fFalse)(Function.const) branch(condition)(left)(right) } @noop - def whenS[A](fbool: F[Boolean])(fa: F[Unit]): F[Unit] = + def whenS[A](fbool: F[Boolean])(fa: => F[Unit]): F[Unit] = ifS(fbool)(fa)(unit) } @@ -57,12 +57,9 @@ object Selective { type TypeClassType <: Selective[F] def self: F[A] val typeClassInstance: TypeClassType - def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = - typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) - def branch[B, C, D](fl: F[B => D])(fr: F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = - typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) - def apS[B, C](fa: F[B])(implicit ev$1: A <:< (B => C)): F[C] = - typeClassInstance.apS[B, C](self.asInstanceOf[F[B => C]])(fa) + def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) + def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) + def apS[B, C](fa: F[B])(implicit ev$1: A <:< (B => C)): F[C] = typeClassInstance.apS[B, C](self.asInstanceOf[F[B => C]])(fa) } trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { type TypeClassType <: Selective[F] @@ -83,4 +80,5 @@ object Selective { /* END OF SIMULACRUM-MANAGED CODE */ /* ======================================================================== */ + } diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index 5ca4d71167..d602cc2597 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -12,10 +12,22 @@ trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = ff.apS(fa) <-> ff.ap(fa) - def selectiveSkipOnRight[A, B](fb: F[B]): IsEq[F[B]] = { - def ope = F.pure(sys.error("ope"): A => B) - fb.map(b => Right(b)).select(ope) <-> fb - } + private def ope[A] = F.pure(sys.error("ope!"): A) + + def selectiveSelectSkip[A, B](fb: F[B]): IsEq[F[B]] = + fb.map(b => Right(b)).select(ope[A => B]) <-> fb + + def selectiveBranchSkipRight[A, B, C](fa: F[A], fl: F[A => C]): IsEq[F[C]] = + fa.map(Left(_)).branch(fl)(ope[B => C]) <-> fa.map(Left(_)).select(fl) + + def selectiveBranchSkipLeft[A, B, C](fb: F[B], fr: F[B => C]): IsEq[F[C]] = + fb.map(Right(_)).branch(ope[A => C])(fr) <-> fb.map(Left(_)).select(fr) + + def selectiveIfSSkipFalse[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = + fa.as(true).ifS(fb)(ope[B]) <-> fa *> fb + + def selectiveIfSSkipTrue[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = + fa.as(false).ifS(ope[B])(fb) <-> fa *> fb } object RigidSelectiveLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index aa06b15500..1da2250e91 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -32,7 +32,11 @@ trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { def props: Seq[(String, Prop)] = Seq( "selective apply" -> forAll(laws.selectiveApply[A, B] _), - "selective skip on right" -> forAll(laws.selectiveSkipOnRight[A, B] _) + "selective select skip" -> forAll(laws.selectiveSelectSkip[A, B] _), + "selective branch skip right" -> forAll(laws.selectiveBranchSkipRight[A, B, C] _), + "selective branch skip left" -> forAll(laws.selectiveBranchSkipLeft[A, B, C] _), + "selective ifS skip true" -> forAll(laws.selectiveIfSSkipTrue[A, B] _), + "selective ifS skip false" -> forAll(laws.selectiveIfSSkipFalse[A, B] _) ) } } From 37594b944bdec602b5fe68927bc1c83318274777 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 16 Dec 2020 18:23:54 -0500 Subject: [PATCH 30/46] Skip effect of whenS when false in rigid selectives --- core/src/main/scala/cats/Selective.scala | 4 ++-- core/src/main/scala/cats/syntax/selective.scala | 6 +++--- laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala | 3 +++ .../scala/cats/laws/discipline/RigidSelectiveTests.scala | 3 ++- .../main/scala/cats/laws/discipline/SelectiveTests.scala | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 56978c98ca..cbd89950c0 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -29,8 +29,8 @@ import scala.annotation.implicitNotFound } @noop - def whenS[A](fbool: F[Boolean])(fa: => F[Unit]): F[Unit] = - ifS(fbool)(fa)(unit) + def whenS[A](fCond: F[Boolean])(fTrue: => F[Unit]): F[Unit] = + ifS(fCond)(fTrue)(unit) } object Selective { diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala index 45ffc07c6a..bc2a3b3723 100644 --- a/core/src/main/scala/cats/syntax/selective.scala +++ b/core/src/main/scala/cats/syntax/selective.scala @@ -6,7 +6,7 @@ trait SelectiveSyntax extends Selective.ToSelectiveOps { new SelectiveBooleanOps(fBool) } -final class SelectiveBooleanOps[F[_]](private val fBool: F[Boolean]) extends AnyVal { - def ifS[A](ifTrue: => F[A])(ifFalse: => F[A])(implicit F: Selective[F]): F[A] = F.ifS(fBool)(ifTrue)(ifFalse) - def whenS[A](fa: F[Unit])(implicit F: Selective[F]): F[Unit] = F.whenS(fBool)(fa) +final class SelectiveBooleanOps[F[_]](private val fCond: F[Boolean]) extends AnyVal { + def ifS[A](fTrue: => F[A])(fFalse: => F[A])(implicit F: Selective[F]): F[A] = F.ifS(fCond)(fTrue)(fFalse) + def whenS[A](fTrue: => F[Unit])(implicit F: Selective[F]): F[Unit] = F.whenS(fCond)(fTrue) } diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index d602cc2597..8042df9487 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -28,6 +28,9 @@ trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { def selectiveIfSSkipTrue[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = fa.as(false).ifS(ope[B])(fb) <-> fa *> fb + + def selectiveWhenSSkip[A](fa: F[A]): IsEq[F[Unit]] = + fa.as(false).whenS(ope[Unit]) <-> fa *> F.unit } object RigidSelectiveLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index 1da2250e91..1004bffee5 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -36,7 +36,8 @@ trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { "selective branch skip right" -> forAll(laws.selectiveBranchSkipRight[A, B, C] _), "selective branch skip left" -> forAll(laws.selectiveBranchSkipLeft[A, B, C] _), "selective ifS skip true" -> forAll(laws.selectiveIfSSkipTrue[A, B] _), - "selective ifS skip false" -> forAll(laws.selectiveIfSSkipFalse[A, B] _) + "selective ifS skip false" -> forAll(laws.selectiveIfSSkipFalse[A, B] _), + "selective whenS skip" -> forAll(laws.selectiveWhenSSkip[A] _) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index 5958e10e48..60f2e3fd27 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -60,7 +60,7 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { f <- Gen.function1(Gen.function1(arbC.arbitrary)(cogenB))(cogenA) } yield laws.F.as(fa, f)) - implicit private def eqFUnit(implicit eqFInt: Eq[F[Int]]): Eq[F[Unit]] = + implicit protected def derivedArbiraryFUnit(implicit eqFInt: Eq[F[Int]]): Eq[F[Unit]] = Eq.by(laws.F.map(_)(_ => 0)) } From b56130d50eefcfbb8e58df352d9a06709d22656f Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Wed, 16 Dec 2020 18:59:03 -0500 Subject: [PATCH 31/46] Replace apS with RigidSelective typeclass --- core/src/main/scala/cats/Monad.scala | 2 +- core/src/main/scala/cats/RigidSelective.scala | 59 +++++++++++++++++++ core/src/main/scala/cats/Selective.scala | 14 ++--- .../scala/cats/laws/RigidSelectiveLaws.scala | 6 +- .../laws/discipline/RigidSelectiveTests.scala | 2 +- 5 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 core/src/main/scala/cats/RigidSelective.scala diff --git a/core/src/main/scala/cats/Monad.scala b/core/src/main/scala/cats/Monad.scala index cb697146da..ad3bc0716f 100644 --- a/core/src/main/scala/cats/Monad.scala +++ b/core/src/main/scala/cats/Monad.scala @@ -13,7 +13,7 @@ import scala.annotation.implicitNotFound * Must obey the laws defined in cats.laws.MonadLaws. */ @implicitNotFound("Could not find an instance of Monad for ${F}") -@typeclass trait Monad[F[_]] extends FlatMap[F] with Selective[F] { +@typeclass trait Monad[F[_]] extends FlatMap[F] with RigidSelective[F] { override def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a))) diff --git a/core/src/main/scala/cats/RigidSelective.scala b/core/src/main/scala/cats/RigidSelective.scala new file mode 100644 index 0000000000..256d57fb3c --- /dev/null +++ b/core/src/main/scala/cats/RigidSelective.scala @@ -0,0 +1,59 @@ +package cats + +import simulacrum.typeclass +import scala.annotation.implicitNotFound + +@implicitNotFound("Could not find an instance of RigidSelective for ${F}") +@typeclass trait RigidSelective[F[_]] extends Selective[F] { + override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = { + val left: F[Either[A => B, B]] = map(ff)(Left(_)) + val right: F[(A => B) => B] = map(fa)((a: A) => _(a)) + select(left)(right) + } +} + +object RigidSelective { + /* ======================================================================== */ + /* THE FOLLOWING CODE IS MANAGED BY SIMULACRUM; PLEASE DO NOT EDIT!!!! */ + /* ======================================================================== */ + + /** + * Summon an instance of [[RigidSelective]] for `F`. + */ + @inline def apply[F[_]](implicit instance: RigidSelective[F]): RigidSelective[F] = instance + + @deprecated("Use cats.syntax object imports", "2.2.0") + object ops { + implicit def toAllRigidSelectiveOps[F[_], A](target: F[A])(implicit tc: RigidSelective[F]): AllOps[F, A] { + type TypeClassType = RigidSelective[F] + } = new AllOps[F, A] { + type TypeClassType = RigidSelective[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } + } + trait Ops[F[_], A] extends Serializable { + type TypeClassType <: RigidSelective[F] + def self: F[A] + val typeClassInstance: TypeClassType + } + trait AllOps[F[_], A] extends Ops[F, A] with Selective.AllOps[F, A] { + type TypeClassType <: RigidSelective[F] + } + trait ToRigidSelectiveOps extends Serializable { + implicit def toRigidSelectiveOps[F[_], A](target: F[A])(implicit tc: RigidSelective[F]): Ops[F, A] { + type TypeClassType = RigidSelective[F] + } = new Ops[F, A] { + type TypeClassType = RigidSelective[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } + } + @deprecated("Use cats.syntax object imports", "2.2.0") + object nonInheritedOps extends ToRigidSelectiveOps + + /* ======================================================================== */ + /* END OF SIMULACRUM-MANAGED CODE */ + /* ======================================================================== */ + +} diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index cbd89950c0..4a00074dfa 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -14,12 +14,6 @@ import scala.annotation.implicitNotFound select(lhs)(fr) } - def apS[A, B](ff: F[A => B])(fa: F[A]): F[B] = { - val left: F[Either[A => B, B]] = map(ff)(Left(_)) - val right: F[(A => B) => B] = map(fa)((a: A) => _(a)) - select(left)(right) - } - @noop def ifS[A](fCond: F[Boolean])(fTrue: => F[A])(fFalse: => F[A]): F[A] = { val condition: F[Either[Unit, Unit]] = map(fCond)(if (_) EitherUtil.leftUnit else EitherUtil.unit) @@ -57,9 +51,10 @@ object Selective { type TypeClassType <: Selective[F] def self: F[A] val typeClassInstance: TypeClassType - def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) - def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) - def apS[B, C](fa: F[B])(implicit ev$1: A <:< (B => C)): F[C] = typeClassInstance.apS[B, C](self.asInstanceOf[F[B => C]])(fa) + def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = + typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) + def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = + typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) } trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { type TypeClassType <: Selective[F] @@ -80,5 +75,4 @@ object Selective { /* END OF SIMULACRUM-MANAGED CODE */ /* ======================================================================== */ - } diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index 8042df9487..5a311c1ea1 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -10,7 +10,11 @@ trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { implicit override def F: Selective[F] def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = - ff.apS(fa) <-> ff.ap(fa) + ff.ap(fa) <-> { + val left: F[Either[A => B, B]] = ff.map(Left(_)) + val right: F[(A => B) => B] = fa.map((a: A) => _(a)) + left.select(right) + } private def ope[A] = F.pure(sys.error("ope!"): A) diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index 1004bffee5..dc6b1a7bc6 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -44,7 +44,7 @@ trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { } object RigidSelectiveTests { - def apply[F[_]: Selective]: RigidSelectiveTests[F] = + def apply[F[_]: RigidSelective]: RigidSelectiveTests[F] = new RigidSelectiveTests[F] { def laws: RigidSelectiveLaws[F] = RigidSelectiveLaws[F] } From 9079e16b0306e9d7cc2ea206ac4fa130a2cd07f9 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 17 Dec 2020 00:25:38 -0500 Subject: [PATCH 32/46] Validated is rigid --- core/src/main/scala/cats/data/Validated.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index fd09899027..b06f758ec2 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -933,7 +933,7 @@ sealed abstract private[data] class ValidatedInstances extends ValidatedInstance implicit def catsDataSelectiveErrorForValidated[E](implicit E: Semigroup[E] - ): Selective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = + ): RigidSelective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = new ValidatedSelective[E] with ApplicativeError[Validated[E, *], E] { def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = fa match { @@ -1044,13 +1044,18 @@ sealed abstract private[data] class ValidatedInstances2 { // scalastyle:off method.length } -private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] with Selective[Validated[E, *]] { +private[data] class ValidatedSelective[E: Semigroup] + extends ValidatedApplicative[E] + with RigidSelective[Validated[E, *]] { override def select[A, B](fab: Validated[E, Either[A, B]])(ff: => Validated[E, A => B]): Validated[E, B] = fab match { case Valid(Left(a)) => ff.map(_(a)) case Valid(Right(b)) => Valid(b) case i @ Invalid(_) => i } + + override def ap[A, B](ff: Validated[E, (A) => B])(fa: Validated[E, A]): Validated[E, B] = + fa.ap(ff)(Semigroup[E]) } private[data] class ValidatedApplicative[E: Semigroup] extends CommutativeApplicative[Validated[E, *]] { From 020a9fb659e78d7493249d1521408879fb20ef9a Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 18 Dec 2020 21:32:39 -0500 Subject: [PATCH 33/46] Revert "Validated is rigid" -- it's not rigid yet This reverts commit 9079e16b0306e9d7cc2ea206ac4fa130a2cd07f9. --- core/src/main/scala/cats/data/Validated.scala | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index b06f758ec2..fd09899027 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -933,7 +933,7 @@ sealed abstract private[data] class ValidatedInstances extends ValidatedInstance implicit def catsDataSelectiveErrorForValidated[E](implicit E: Semigroup[E] - ): RigidSelective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = + ): Selective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = new ValidatedSelective[E] with ApplicativeError[Validated[E, *], E] { def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = fa match { @@ -1044,18 +1044,13 @@ sealed abstract private[data] class ValidatedInstances2 { // scalastyle:off method.length } -private[data] class ValidatedSelective[E: Semigroup] - extends ValidatedApplicative[E] - with RigidSelective[Validated[E, *]] { +private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] with Selective[Validated[E, *]] { override def select[A, B](fab: Validated[E, Either[A, B]])(ff: => Validated[E, A => B]): Validated[E, B] = fab match { case Valid(Left(a)) => ff.map(_(a)) case Valid(Right(b)) => Valid(b) case i @ Invalid(_) => i } - - override def ap[A, B](ff: Validated[E, (A) => B])(fa: Validated[E, A]): Validated[E, B] = - fa.ap(ff)(Semigroup[E]) } private[data] class ValidatedApplicative[E: Semigroup] extends CommutativeApplicative[Validated[E, *]] { From 92f262fc634f162cedec21cb121ff8c917f4e5ba Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 18 Dec 2020 22:21:44 -0500 Subject: [PATCH 34/46] Push select down to Apply --- core/src/main/scala/cats/Apply.scala | 5 +++++ core/src/main/scala/cats/Selective.scala | 3 --- laws/src/main/scala/cats/laws/ApplyLaws.scala | 8 ++++++++ .../main/scala/cats/laws/SelectiveLaws.scala | 7 ------- .../cats/laws/discipline/ApplyTests.scala | 19 ++++++++++++++++++- .../cats/laws/discipline/SelectiveTests.scala | 14 +------------- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index ec1898a7f7..6d198f2c8b 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -258,6 +258,9 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit ap2(map(fcond)(ite))(ifTrue, ifFalse) } + def select[A, B](fab: F[Either[A, B]])(ff: => F[A => B]): F[B] = + selectA(fab)(ff) + def selectA[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = map2(fab, ff) { case (Left(a), f) => f(a) @@ -317,6 +320,8 @@ object Apply { typeClassInstance.ap2[B, C, D](self.asInstanceOf[F[(B, C) => D]])(fa, fb) def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] = typeClassInstance.map2[A, B, C](self, fb)(f) def map2Eval[B, C](fb: Eval[F[B]])(f: (A, B) => C): Eval[F[C]] = typeClassInstance.map2Eval[A, B, C](self, fb)(f) + def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = + typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) def selectA[B, C](ff: F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = typeClassInstance.selectA[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) } diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 4a00074dfa..3bc78bf181 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -5,7 +5,6 @@ import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") @typeclass trait Selective[F[_]] extends Applicative[F] { - def select[A, B](fab: F[Either[A, B]])(ff: => F[A => B]): F[B] def branch[A, B, C](fab: F[Either[A, B]])(fl: => F[A => C])(fr: => F[B => C]): F[C] = { val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) @@ -51,8 +50,6 @@ object Selective { type TypeClassType <: Selective[F] def self: F[A] val typeClassInstance: TypeClassType - def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = - typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) } diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index 5aa1338c0a..b373dbb585 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -2,6 +2,7 @@ package cats package laws import cats.syntax.apply._ +import cats.syntax.either._ import cats.syntax.functor._ /** @@ -27,6 +28,13 @@ trait ApplyLaws[F[_]] extends FunctorLaws[F] with SemigroupalLaws[F] { def productLConsistency[A, B](fa: F[A], fb: F[B]): IsEq[F[A]] = F.productL(fa)(fb) <-> F.map2(fa, fb)((a, _) => a) + def selectAssociativity[A, B, C](fa: F[Either[A, B]], fb: F[Either[C, A => B]], fc: F[C => A => B]): IsEq[F[B]] = { + val fa0 = fa.map(_.map(_.asRight[(C, A)])) + val fb0 = fb.map { either => (a: A) => either.bimap(c => (c, a), f => f(a)) } + val fc0 = fc.map(Function.uncurried(_).tupled) + fa.select(fb.select(fc)) <-> fa0.select(fb0).select(fc0) + } + def selectAConsistency[A, B](fab: F[Either[A, B]], ff: F[A => B]): IsEq[F[B]] = F.selectA(fab)(ff) <-> F.map2(fab, ff) { case (Left(a), f) => f(a) diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index 10425c9381..f101537f8e 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -15,13 +15,6 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) - def selectiveAssociativity[A, B, C](fa: F[Either[A, B]], fb: F[Either[C, A => B]], fc: F[C => A => B]): IsEq[F[B]] = { - val fa0 = fa.map(_.map(_.asRight[(C, A)])) - val fb0 = fb.map { either => (a: A) => either.bimap(c => (c, a), f => f(a)) } - val fc0 = fc.map(Function.uncurried(_).tupled) - fa.select(fb.select(fc)) <-> fa0.select(fb0).select(fc0) - } - def selectiveBranchConsistency[A, B, C](fab: F[Either[A, B]], fl: F[A => C], fr: F[B => C]): IsEq[F[C]] = { fab.branch(fl)(fr) <-> { val lhs = { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index 952b40ac45..7eff2b760b 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -23,7 +23,9 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { EqFC: Eq[F[C]], EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] - ): RuleSet = + ): RuleSet = { + // We don't have an F[B]. This is an odious hack to preserve bincompat. + implicit val EqFB: Eq[F[B]] = Eq.by((fb: F[B]) => laws.F.map(fb)((null.asInstanceOf[A], _, null.asInstanceOf[C]))) new RuleSet { val name = "apply" val parents = Seq(functor[A, B, C], semigroupal[A, B, C]) @@ -34,11 +36,14 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { "map2/map2Eval consistency" -> forAll(laws.map2EvalConsistency[A, B, C] _), "productR consistent map2" -> forAll(laws.productRConsistency[A, C] _), "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _), + "select associativity" -> forAll(laws.selectAssociativity[A, B, C] _), "selectA consistent map2" -> forAll(laws.selectAConsistency[A, C] _) ) } + } // Derived implicits to preserve bincompat + implicit protected def derivedArbitraryEither[A, B](implicit arbFA: Arbitrary[F[A]], arbFB: Arbitrary[F[B]] @@ -58,6 +63,18 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { fAToB <- arbFAtoB.arbitrary fBToC <- arbFBtoC.arbitrary } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) + + implicit protected def derivedAtoBtoC[A, B, C](implicit + arbFA: Arbitrary[F[A]], + arbC: Arbitrary[C], + cogenA: Cogen[A], + cogenB: Cogen[B] + ): Arbitrary[F[A => B => C]] = + Arbitrary(for { + fa <- arbFA.arbitrary + f <- Gen.function1(Gen.function1(arbC.arbitrary)(cogenB))(cogenA) + } yield laws.F.as(fa, f)) + } object ApplyTests { diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index 60f2e3fd27..4546140663 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -3,7 +3,7 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} +import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { @@ -35,7 +35,6 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { Seq( "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _), - "selective associativity" -> forAll(laws.selectiveAssociativity[A, B, C] _), "selective branch consistency" -> forAll(laws.selectiveBranchConsistency[A, B, C] _), "selective ifS consistency" -> forAll(laws.selectiveIfSConsistency[A] _), "selective whenS consistency" -> forAll(laws.selectiveWhenSConsistency[A] _) @@ -49,17 +48,6 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { b <- arbB.arbitrary } yield laws.F.as(fa, b)) - implicit private def arbFAtoBtoC[A, B, C](implicit - arbFA: Arbitrary[F[A]], - arbC: Arbitrary[C], - cogenA: Cogen[A], - cogenB: Cogen[B] - ): Arbitrary[F[A => B => C]] = - Arbitrary(for { - fa <- arbFA.arbitrary - f <- Gen.function1(Gen.function1(arbC.arbitrary)(cogenB))(cogenA) - } yield laws.F.as(fa, f)) - implicit protected def derivedArbiraryFUnit(implicit eqFInt: Eq[F[Int]]): Eq[F[Unit]] = Eq.by(laws.F.map(_)(_ => 0)) } From 762dd7dbea5c363e2fbd98d6920e21a7afbf2081 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 18 Dec 2020 22:45:55 -0500 Subject: [PATCH 35/46] Push branch and ifS down to Apply --- core/src/main/scala/cats/Apply.scala | 17 ++++ core/src/main/scala/cats/Selective.scala | 18 ---- core/src/main/scala/cats/syntax/apply.scala | 7 ++ .../main/scala/cats/syntax/selective.scala | 1 - laws/src/main/scala/cats/laws/ApplyLaws.scala | 20 ++++ .../main/scala/cats/laws/SelectiveLaws.scala | 20 ---- .../cats/laws/discipline/ApplyTests.scala | 93 +++++++++++++------ .../laws/discipline/RigidSelectiveTests.scala | 6 ++ .../cats/laws/discipline/SelectiveTests.scala | 31 ++++--- 9 files changed, 133 insertions(+), 80 deletions(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index 6d198f2c8b..5c81f81410 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -266,6 +266,21 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit case (Left(a), f) => f(a) case (Right(b), _) => b } + + def branch[A, B, C](fab: F[Either[A, B]])(fl: => F[A => C])(fr: => F[B => C]): F[C] = { + val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) + def innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) + val lhs = select(innerLhs)(innerRhs) + select(lhs)(fr) + } + + @noop + def ifS[A](fCond: F[Boolean])(fTrue: => F[A])(fFalse: => F[A]): F[A] = { + val condition: F[Either[Unit, Unit]] = map(fCond)(if (_) EitherUtil.leftUnit else EitherUtil.unit) + def left: F[Unit => A] = map(fTrue)(Function.const) + def right: F[Unit => A] = map(fFalse)(Function.const) + branch(condition)(left)(right) + } } object Apply { @@ -324,6 +339,8 @@ object Apply { typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) def selectA[B, C](ff: F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = typeClassInstance.selectA[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) + def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = + typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) } trait AllOps[F[_], A] extends Ops[F, A] with Functor.AllOps[F, A] with InvariantSemigroupal.AllOps[F, A] { type TypeClassType <: Apply[F] diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 3bc78bf181..3c7aed1e61 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -5,22 +5,6 @@ import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") @typeclass trait Selective[F[_]] extends Applicative[F] { - - def branch[A, B, C](fab: F[Either[A, B]])(fl: => F[A => C])(fr: => F[B => C]): F[C] = { - val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) - def innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) - val lhs = select(innerLhs)(innerRhs) - select(lhs)(fr) - } - - @noop - def ifS[A](fCond: F[Boolean])(fTrue: => F[A])(fFalse: => F[A]): F[A] = { - val condition: F[Either[Unit, Unit]] = map(fCond)(if (_) EitherUtil.leftUnit else EitherUtil.unit) - def left: F[Unit => A] = map(fTrue)(Function.const) - def right: F[Unit => A] = map(fFalse)(Function.const) - branch(condition)(left)(right) - } - @noop def whenS[A](fCond: F[Boolean])(fTrue: => F[Unit]): F[Unit] = ifS(fCond)(fTrue)(unit) @@ -50,8 +34,6 @@ object Selective { type TypeClassType <: Selective[F] def self: F[A] val typeClassInstance: TypeClassType - def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = - typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) } trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { type TypeClassType <: Selective[F] diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index 5966eb3836..0d9f6dd89a 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -12,6 +12,9 @@ trait ApplySyntax extends TupleSemigroupalSyntax { implicit final def catsSyntaxApplyOps[F[_], A](fa: F[A]): ApplyOps[F, A] = new ApplyOps(fa) + + implicit final def catsSyntaxApplyBooleanOps[F[_]](fBool: F[Boolean]): ApplyBooleanOps[F] = + new ApplyBooleanOps(fBool) } private[syntax] trait ApplySyntaxBinCompat0 { @@ -68,3 +71,7 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { @inline private[syntax] def forEffect[B](fb: F[B])(implicit F: Apply[F]): F[A] = F.productL(fa)(fb) } + +final class ApplyBooleanOps[F[_]](private val fCond: F[Boolean]) extends AnyVal { + def ifS[A](fTrue: => F[A])(fFalse: => F[A])(implicit F: Apply[F]): F[A] = F.ifS(fCond)(fTrue)(fFalse) +} diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala index bc2a3b3723..e817de1753 100644 --- a/core/src/main/scala/cats/syntax/selective.scala +++ b/core/src/main/scala/cats/syntax/selective.scala @@ -7,6 +7,5 @@ trait SelectiveSyntax extends Selective.ToSelectiveOps { } final class SelectiveBooleanOps[F[_]](private val fCond: F[Boolean]) extends AnyVal { - def ifS[A](fTrue: => F[A])(fFalse: => F[A])(implicit F: Selective[F]): F[A] = F.ifS(fCond)(fTrue)(fFalse) def whenS[A](fTrue: => F[Unit])(implicit F: Selective[F]): F[Unit] = F.whenS(fCond)(fTrue) } diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index b373dbb585..7175a13b94 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -35,6 +35,26 @@ trait ApplyLaws[F[_]] extends FunctorLaws[F] with SemigroupalLaws[F] { fa.select(fb.select(fc)) <-> fa0.select(fb0).select(fc0) } + def branchConsistency[A, B, C](fab: F[Either[A, B]], fl: F[A => C], fr: F[B => C]): IsEq[F[C]] = { + fab.branch(fl)(fr) <-> { + val lhs = { + val innerLhs: F[Either[A, Either[B, C]]] = F.map(fab)(_.map(Left(_))) + val innerRhs: F[A => Either[B, C]] = F.map(fl)(_.andThen(Right(_))) + F.select(innerLhs)(innerRhs) + } + F.select(lhs)(fr) + } + } + + def ifSConsistency[A](fb: F[Boolean], ft: F[A], ff: F[A]): IsEq[F[A]] = { + fb.ifS(ft)(ff) <-> { + val condition: F[Either[Unit, Unit]] = F.map(fb)(p => if (p) Left(()) else Right(())) + val left: F[Unit => A] = F.map(ft)(Function.const) + val right: F[Unit => A] = F.map(ff)(Function.const) + F.branch(condition)(left)(right) + } + } + def selectAConsistency[A, B](fab: F[Either[A, B]], ff: F[A => B]): IsEq[F[B]] = F.selectA(fab)(ff) <-> F.map2(fab, ff) { case (Left(a), f) => f(a) diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index f101537f8e..c6ab028a15 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -15,26 +15,6 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) - def selectiveBranchConsistency[A, B, C](fab: F[Either[A, B]], fl: F[A => C], fr: F[B => C]): IsEq[F[C]] = { - fab.branch(fl)(fr) <-> { - val lhs = { - val innerLhs: F[Either[A, Either[B, C]]] = F.map(fab)(_.map(Left(_))) - val innerRhs: F[A => Either[B, C]] = F.map(fl)(_.andThen(Right(_))) - F.select(innerLhs)(innerRhs) - } - F.select(lhs)(fr) - } - } - - def selectiveIfSConsistency[A](fb: F[Boolean], ft: F[A], ff: F[A]): IsEq[F[A]] = { - fb.ifS(ft)(ff) <-> { - val condition: F[Either[Unit, Unit]] = F.map(fb)(p => if (p) Left(()) else Right(())) - val left: F[Unit => A] = F.map(ft)(Function.const) - val right: F[Unit => A] = F.map(ff)(Function.const) - F.branch(condition)(left)(right) - } - } - def selectiveWhenSConsistency[A](fb: F[Boolean], fa: F[Unit]): IsEq[F[Unit]] = { fb.whenS(fa) <-> F.ifS(fb)(fa)(F.unit) } diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index 7eff2b760b..18cf10c76b 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -24,8 +24,35 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] ): RuleSet = { - // We don't have an F[B]. This is an odious hack to preserve bincompat. + // Derive implicits required after bincompat was locked in for 2.0 + + implicit val ArbFCond: Arbitrary[F[Boolean]] = Arbitrary(for { + fa <- ArbFA.arbitrary + b <- Arbitrary.arbitrary[Boolean] + } yield laws.F.as(fa, b)) + + implicit val ArbFAB: Arbitrary[F[Either[A, B]]] = Arbitrary( + Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), + ArbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) + ) + ) + + implicit val ArbFAC: Arbitrary[F[Either[A, C]]] = Arbitrary( + Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[C])), + ArbFC.arbitrary.map(fc => laws.F.map(fc)(_.asRight[A])) + ) + ) + + implicit val ArbFAtoC: Arbitrary[F[A => C]] = + Arbitrary(for { + fAToB <- ArbFAtoB.arbitrary + fBToC <- ArbFBtoC.arbitrary + } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) + implicit val EqFB: Eq[F[B]] = Eq.by((fb: F[B]) => laws.F.map(fb)((null.asInstanceOf[A], _, null.asInstanceOf[C]))) + new RuleSet { val name = "apply" val parents = Seq(functor[A, B, C], semigroupal[A, B, C]) @@ -36,7 +63,8 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { "map2/map2Eval consistency" -> forAll(laws.map2EvalConsistency[A, B, C] _), "productR consistent map2" -> forAll(laws.productRConsistency[A, C] _), "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _), - "select associativity" -> forAll(laws.selectAssociativity[A, B, C] _), + "branch/select consistency" -> forAll(laws.branchConsistency[A, B, C] _), + "ifS/branch consistency" -> forAll(laws.ifSConsistency[A] _), "selectA consistent map2" -> forAll(laws.selectAConsistency[A, C] _) ) } @@ -44,37 +72,42 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { // Derived implicits to preserve bincompat - implicit protected def derivedArbitraryEither[A, B](implicit - arbFA: Arbitrary[F[A]], - arbFB: Arbitrary[F[B]] - ): Arbitrary[F[Either[A, B]]] = { - Arbitrary( - Gen.oneOf(arbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), - arbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) - ) - ) - } +// implicit protected def derivedArbitraryEither[A, B](implicit +// arbFA: Arbitrary[F[A]], +// arbFB: Arbitrary[F[B]] +// ): Arbitrary[F[Either[A, B]]] = { +// Arbitrary( +// Gen.oneOf(arbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), +// arbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) +// ) +// ) +// } - implicit protected def derivedArbitraryFunctionComposition[A, B, C](implicit - arbFAtoB: Arbitrary[F[A => B]], - arbFBtoC: Arbitrary[F[B => C]] - ): Arbitrary[F[A => C]] = - Arbitrary(for { - fAToB <- arbFAtoB.arbitrary - fBToC <- arbFBtoC.arbitrary - } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) +// implicit protected def derivedArbitraryFunctionComposition[A, B, C](implicit +// arbFAtoB: Arbitrary[F[A => B]], +// arbFBtoC: Arbitrary[F[B => C]] +// ): Arbitrary[F[A => C]] = +// Arbitrary(for { +// fAToB <- arbFAtoB.arbitrary +// fBToC <- arbFBtoC.arbitrary +// } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) - implicit protected def derivedAtoBtoC[A, B, C](implicit - arbFA: Arbitrary[F[A]], - arbC: Arbitrary[C], - cogenA: Cogen[A], - cogenB: Cogen[B] - ): Arbitrary[F[A => B => C]] = - Arbitrary(for { - fa <- arbFA.arbitrary - f <- Gen.function1(Gen.function1(arbC.arbitrary)(cogenB))(cogenA) - } yield laws.F.as(fa, f)) +// implicit protected def derivedArbitraryAtoBtoC[A, B, C](implicit +// arbFA: Arbitrary[F[A]], +// arbC: Arbitrary[C], +// cogenA: Cogen[A], +// cogenB: Cogen[B] +// ): Arbitrary[F[A => B => C]] = +// Arbitrary(for { +// fa <- arbFA.arbitrary +// f <- Gen.function1(Gen.function1(arbC.arbitrary)(cogenB))(cogenA) +// } yield laws.F.as(fa, f)) +// implicit def derivedArbitraryFB[A, B](implicit arbFA: Arbitrary[F[A]], arbB: Arbitrary[B]): Arbitrary[F[B]] = +// Arbitrary(for { +// fa <- arbFA.arbitrary +// b <- arbB.arbitrary +// } yield laws.F.as(fa, b)) } object ApplyTests { diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index dc6b1a7bc6..336fb5cea8 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -25,6 +25,12 @@ trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { EqFInt: Eq[F[Int]], iso: Isomorphisms[F] ): RuleSet = { + implicit val ArbFAtoC: Arbitrary[F[A => C]] = + Arbitrary(for { + fAToB <- ArbFAtoB.arbitrary + fBToC <- ArbFBtoC.arbitrary + } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) + new RuleSet { def name: String = "rigidSelective" def bases: Seq[(String, RuleSet)] = Nil diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index 4546140663..0da755553c 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -3,7 +3,8 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import org.scalacheck.{Arbitrary, Cogen, Prop} +import cats.syntax.either._ +import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} import Prop._ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { @@ -25,8 +26,24 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { EqFInt: Eq[F[Int]], iso: Isomorphisms[F] ): RuleSet = { - implicit val ArbFBool: Arbitrary[F[Boolean]] = arbFB[A, Boolean] - implicit val ArbFUnit: Arbitrary[F[Unit]] = arbFB[A, Unit] + // Derive implicits required after bincompat was locked in for 2.0 + + implicit val ArbFCond: Arbitrary[F[Boolean]] = Arbitrary(for { + fa <- ArbFA.arbitrary + b <- Arbitrary.arbitrary[Boolean] + } yield laws.F.as(fa, b)) + + implicit val ArbFUnit: Arbitrary[F[Unit]] = Arbitrary(for { + fa <- ArbFA.arbitrary + } yield laws.F.as(fa, ())) + + implicit val ArbFAA: Arbitrary[F[Either[A, A]]] = Arbitrary( + Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) + ) + ) + new RuleSet { def name: String = "selective" def bases: Seq[(String, RuleSet)] = Nil @@ -35,19 +52,11 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { Seq( "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _), - "selective branch consistency" -> forAll(laws.selectiveBranchConsistency[A, B, C] _), - "selective ifS consistency" -> forAll(laws.selectiveIfSConsistency[A] _), "selective whenS consistency" -> forAll(laws.selectiveWhenSConsistency[A] _) ) } } - private def arbFB[A, B](implicit arbFA: Arbitrary[F[A]], arbB: Arbitrary[B]): Arbitrary[F[B]] = - Arbitrary(for { - fa <- arbFA.arbitrary - b <- arbB.arbitrary - } yield laws.F.as(fa, b)) - implicit protected def derivedArbiraryFUnit(implicit eqFInt: Eq[F[Int]]): Eq[F[Unit]] = Eq.by(laws.F.map(_)(_ => 0)) } From 691144eff39af3eebcc843a215028453681e9d4e Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 18 Dec 2020 23:20:26 -0500 Subject: [PATCH 36/46] Push whenS down to Applicative --- core/src/main/scala/cats/Applicative.scala | 28 ++++++++++--------- core/src/main/scala/cats/Selective.scala | 8 ++---- .../main/scala/cats/syntax/applicative.scala | 6 ++++ .../main/scala/cats/syntax/selective.scala | 9 +----- .../scala/cats/laws/ApplicativeLaws.scala | 5 ++++ .../main/scala/cats/laws/SelectiveLaws.scala | 4 --- .../laws/discipline/ApplicativeTests.scala | 20 +++++++++++-- .../laws/discipline/RigidSelectiveTests.scala | 5 ++++ .../cats/laws/discipline/SelectiveTests.scala | 6 +--- 9 files changed, 53 insertions(+), 38 deletions(-) diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index c04a39bd35..9e64e18ab3 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -1,7 +1,7 @@ package cats import cats.arrow.Arrow -import simulacrum.typeclass +import simulacrum.{noop, typeclass} import scala.annotation.implicitNotFound /** @@ -185,6 +185,10 @@ import scala.annotation.implicitNotFound */ def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = if (cond) void(f) else unit + + @noop + def whenS[A](fCond: F[Boolean])(fTrue: => F[Unit]): F[Unit] = + ifS(fCond)(fTrue)(unit) } object Applicative { @@ -242,12 +246,11 @@ object Applicative { object ops { implicit def toAllApplicativeOps[F[_], A](target: F[A])(implicit tc: Applicative[F]): AllOps[F, A] { type TypeClassType = Applicative[F] - } = - new AllOps[F, A] { - type TypeClassType = Applicative[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new AllOps[F, A] { + type TypeClassType = Applicative[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } trait Ops[F[_], A] extends Serializable { type TypeClassType <: Applicative[F] @@ -260,12 +263,11 @@ object Applicative { trait ToApplicativeOps extends Serializable { implicit def toApplicativeOps[F[_], A](target: F[A])(implicit tc: Applicative[F]): Ops[F, A] { type TypeClassType = Applicative[F] - } = - new Ops[F, A] { - type TypeClassType = Applicative[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new Ops[F, A] { + type TypeClassType = Applicative[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } @deprecated("Use cats.syntax object imports", "2.2.0") object nonInheritedOps extends ToApplicativeOps diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala index 3c7aed1e61..f4c335f482 100644 --- a/core/src/main/scala/cats/Selective.scala +++ b/core/src/main/scala/cats/Selective.scala @@ -1,14 +1,10 @@ package cats -import simulacrum.{noop, typeclass} +import simulacrum.{typeclass} import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of Selective for ${F}") -@typeclass trait Selective[F[_]] extends Applicative[F] { - @noop - def whenS[A](fCond: F[Boolean])(fTrue: => F[Unit]): F[Unit] = - ifS(fCond)(fTrue)(unit) -} +@typeclass trait Selective[F[_]] extends Applicative[F] {} object Selective { /* ======================================================================== */ diff --git a/core/src/main/scala/cats/syntax/applicative.scala b/core/src/main/scala/cats/syntax/applicative.scala index 4e2a271b02..b010b3fab8 100644 --- a/core/src/main/scala/cats/syntax/applicative.scala +++ b/core/src/main/scala/cats/syntax/applicative.scala @@ -6,6 +6,8 @@ trait ApplicativeSyntax { new ApplicativeIdOps[A](a) implicit final def catsSyntaxApplicative[F[_], A](fa: F[A]): ApplicativeOps[F, A] = new ApplicativeOps[F, A](fa) + implicit final def catsSyntaxApplicativeBoolean[F[_]](fBool: F[Boolean]): ApplicativeBooleanOps[F] = + new ApplicativeBooleanOps(fBool) } final class ApplicativeIdOps[A](private val a: A) extends AnyVal { @@ -17,3 +19,7 @@ final class ApplicativeOps[F[_], A](private val fa: F[A]) extends AnyVal { def unlessA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.unlessA(cond)(fa) def whenA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.whenA(cond)(fa) } + +final class ApplicativeBooleanOps[F[_]](private val fCond: F[Boolean]) extends AnyVal { + def whenS[A](fTrue: => F[Unit])(implicit F: Applicative[F]): F[Unit] = F.whenS(fCond)(fTrue) +} diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala index e817de1753..77fa8d994d 100644 --- a/core/src/main/scala/cats/syntax/selective.scala +++ b/core/src/main/scala/cats/syntax/selective.scala @@ -1,11 +1,4 @@ package cats package syntax -trait SelectiveSyntax extends Selective.ToSelectiveOps { - implicit final def catsSyntaxSelectiveBooleanOps[F[_]](fBool: F[Boolean]): SelectiveBooleanOps[F] = - new SelectiveBooleanOps(fBool) -} - -final class SelectiveBooleanOps[F[_]](private val fCond: F[Boolean]) extends AnyVal { - def whenS[A](fTrue: => F[Unit])(implicit F: Selective[F]): F[Unit] = F.whenS(fCond)(fTrue) -} +trait SelectiveSyntax extends Selective.ToSelectiveOps diff --git a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala index a1405f09a0..4f828c8589 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala @@ -2,6 +2,7 @@ package cats package laws import cats.syntax.apply._ +import cats.syntax.applicative._ import cats.syntax.functor._ /** @@ -46,6 +47,10 @@ trait ApplicativeLaws[F[_]] extends ApplyLaws[F] { def monoidalRightIdentity[A](fa: F[A]): (F[(A, Unit)], F[A]) = (F.product(fa, F.pure(())), fa) + + def whenSIfSConsistency[A](fb: F[Boolean], fa: F[Unit]): IsEq[F[Unit]] = { + fb.whenS(fa) <-> fb.ifS(fa)(F.unit) + } } object ApplicativeLaws { diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index c6ab028a15..dfc3c0393f 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -14,10 +14,6 @@ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) - - def selectiveWhenSConsistency[A](fb: F[Boolean], fa: F[Unit]): IsEq[F[Unit]] = { - fb.whenS(fa) <-> F.ifS(fb)(fa)(F.unit) - } } object SelectiveLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala index 510024fa04..4a97b78172 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala @@ -23,7 +23,21 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { EqFC: Eq[F[C]], EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] - ): RuleSet = + ): RuleSet = { + implicit val ArbFCond: Arbitrary[F[Boolean]] = Arbitrary(for { + fa <- ArbFA.arbitrary + b <- Arbitrary.arbitrary[Boolean] + } yield laws.F.as(fa, b)) + + implicit val ArbFUnit: Arbitrary[F[Unit]] = Arbitrary(for { + fa <- ArbFA.arbitrary + } yield laws.F.as(fa, ())) + + implicit val EqFUnit: Eq[F[Unit]] = { + val a = Arbitrary.arbitrary[A].retryUntil(_ => true).sample.get + Eq.by(laws.F.map(_)(_ => a)) + } + new DefaultRuleSet( name = "applicative", parent = Some(apply[A, B, C]), @@ -34,8 +48,10 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { "applicative unit" -> forAll(laws.applicativeUnit[A] _), "ap consistent with product + map" -> forAll(laws.apProductConsistent[A, B] _), "monoidal left identity" -> forAll((fa: F[A]) => iso.leftIdentity(laws.monoidalLeftIdentity(fa))), - "monoidal right identity" -> forAll((fa: F[A]) => iso.rightIdentity(laws.monoidalRightIdentity(fa))) + "monoidal right identity" -> forAll((fa: F[A]) => iso.rightIdentity(laws.monoidalRightIdentity(fa))), + "whenS/ifS consistency" -> forAll(laws.whenSIfSConsistency[A] _) ) + } } object ApplicativeTests { diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index 336fb5cea8..6d69908ad8 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -31,6 +31,11 @@ trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { fBToC <- ArbFBtoC.arbitrary } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) + implicit val EqFUnit: Eq[F[Unit]] = { + val a = Arbitrary.arbitrary[A].retryUntil(_ => true).sample.get + Eq.by(laws.F.map(_)(_ => null.asInstanceOf[A])) + } + new RuleSet { def name: String = "rigidSelective" def bases: Seq[(String, RuleSet)] = Nil diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index 0da755553c..744c658023 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -51,14 +51,10 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { def props: Seq[(String, Prop)] = Seq( "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), - "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _), - "selective whenS consistency" -> forAll(laws.selectiveWhenSConsistency[A] _) + "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _) ) } } - - implicit protected def derivedArbiraryFUnit(implicit eqFInt: Eq[F[Int]]): Eq[F[Unit]] = - Eq.by(laws.F.map(_)(_ => 0)) } object SelectiveTests { From 3e31609623d7c804c9ae8ce503e54c0c29748214 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 18 Dec 2020 23:24:40 -0500 Subject: [PATCH 37/46] Push identity and distributivity laws to Applicative --- laws/src/main/scala/cats/laws/ApplicativeLaws.scala | 6 ++++++ laws/src/main/scala/cats/laws/SelectiveLaws.scala | 8 -------- .../cats/laws/discipline/ApplicativeTests.scala | 12 +++++++++++- .../scala/cats/laws/discipline/SelectiveTests.scala | 13 +------------ 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala index 4f828c8589..4424e8e1ad 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala @@ -48,6 +48,12 @@ trait ApplicativeLaws[F[_]] extends ApplyLaws[F] { def monoidalRightIdentity[A](fa: F[A]): (F[(A, Unit)], F[A]) = (F.product(fa, F.pure(())), fa) + def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = + faa.select[A, A](F.pure(identity)) <-> faa.map(_.merge) + + def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = + F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) + def whenSIfSConsistency[A](fb: F[Boolean], fa: F[Unit]): IsEq[F[Unit]] = { fb.whenS(fa) <-> fb.ifS(fa)(F.unit) } diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala index dfc3c0393f..4d44118d36 100644 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -1,19 +1,11 @@ package cats package laws -import cats.syntax.all._ - /** * Laws that must be obeyed by any `Selective`. */ trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { implicit override def F: Selective[F] - - def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = - faa.select[A, A](F.pure(identity)) <-> faa.map(_.merge) - - def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = - F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) } object SelectiveLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala index 4a97b78172..18ca3e8acf 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala @@ -3,7 +3,8 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import org.scalacheck.{Arbitrary, Cogen, Prop} +import cats.syntax.either._ +import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} import Prop._ trait ApplicativeTests[F[_]] extends ApplyTests[F] { @@ -33,6 +34,13 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { fa <- ArbFA.arbitrary } yield laws.F.as(fa, ())) + implicit val ArbFAA: Arbitrary[F[Either[A, A]]] = Arbitrary( + Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) + ) + ) + implicit val EqFUnit: Eq[F[Unit]] = { val a = Arbitrary.arbitrary[A].retryUntil(_ => true).sample.get Eq.by(laws.F.map(_)(_ => a)) @@ -49,6 +57,8 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { "ap consistent with product + map" -> forAll(laws.apProductConsistent[A, B] _), "monoidal left identity" -> forAll((fa: F[A]) => iso.leftIdentity(laws.monoidalLeftIdentity(fa))), "monoidal right identity" -> forAll((fa: F[A]) => iso.rightIdentity(laws.monoidalRightIdentity(fa))), + "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), + "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _), "whenS/ifS consistency" -> forAll(laws.whenSIfSConsistency[A] _) ) } diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index 744c658023..7e97892f8a 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -3,9 +3,7 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import cats.syntax.either._ -import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} -import Prop._ +import org.scalacheck.{Arbitrary, Cogen, Prop} trait SelectiveTests[F[_]] extends ApplicativeTests[F] { def laws: SelectiveLaws[F] @@ -37,21 +35,12 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { fa <- ArbFA.arbitrary } yield laws.F.as(fa, ())) - implicit val ArbFAA: Arbitrary[F[Either[A, A]]] = Arbitrary( - Gen.oneOf( - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) - ) - ) - new RuleSet { def name: String = "selective" def bases: Seq[(String, RuleSet)] = Nil def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), - "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _) ) } } From 6b496ec2db02ea66a2ac1b8e18fe3c5d3b338048 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sat, 19 Dec 2020 00:01:08 -0500 Subject: [PATCH 38/46] Remove Selective --- core/src/main/scala/cats/Monad.scala | 2 +- core/src/main/scala/cats/RigidSelective.scala | 4 +- core/src/main/scala/cats/Selective.scala | 53 ------------------ core/src/main/scala/cats/data/Validated.scala | 6 +-- core/src/main/scala/cats/syntax/all.scala | 1 - .../main/scala/cats/syntax/selective.scala | 4 -- .../scala/cats/laws/RigidSelectiveLaws.scala | 8 +-- .../main/scala/cats/laws/SelectiveLaws.scala | 14 ----- .../laws/discipline/RigidSelectiveTests.scala | 4 +- .../cats/laws/discipline/SelectiveTests.scala | 54 ------------------- .../scala/cats/tests/ValidatedSuite.scala | 4 -- 11 files changed, 12 insertions(+), 142 deletions(-) delete mode 100644 core/src/main/scala/cats/Selective.scala delete mode 100644 core/src/main/scala/cats/syntax/selective.scala delete mode 100644 laws/src/main/scala/cats/laws/SelectiveLaws.scala delete mode 100644 laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala diff --git a/core/src/main/scala/cats/Monad.scala b/core/src/main/scala/cats/Monad.scala index ad3bc0716f..ec214c46b2 100644 --- a/core/src/main/scala/cats/Monad.scala +++ b/core/src/main/scala/cats/Monad.scala @@ -183,7 +183,7 @@ object Monad { def iterateWhile(p: A => Boolean): F[A] = typeClassInstance.iterateWhile[A](self)(p) def iterateUntil(p: A => Boolean): F[A] = typeClassInstance.iterateUntil[A](self)(p) } - trait AllOps[F[_], A] extends Ops[F, A] with FlatMap.AllOps[F, A] with Selective.AllOps[F, A] { + trait AllOps[F[_], A] extends Ops[F, A] with FlatMap.AllOps[F, A] with RigidSelective.AllOps[F, A] { type TypeClassType <: Monad[F] } trait ToMonadOps extends Serializable { diff --git a/core/src/main/scala/cats/RigidSelective.scala b/core/src/main/scala/cats/RigidSelective.scala index 256d57fb3c..19e7b0973c 100644 --- a/core/src/main/scala/cats/RigidSelective.scala +++ b/core/src/main/scala/cats/RigidSelective.scala @@ -4,7 +4,7 @@ import simulacrum.typeclass import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of RigidSelective for ${F}") -@typeclass trait RigidSelective[F[_]] extends Selective[F] { +@typeclass trait RigidSelective[F[_]] extends Applicative[F] { override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = { val left: F[Either[A => B, B]] = map(ff)(Left(_)) val right: F[(A => B) => B] = map(fa)((a: A) => _(a)) @@ -37,7 +37,7 @@ object RigidSelective { def self: F[A] val typeClassInstance: TypeClassType } - trait AllOps[F[_], A] extends Ops[F, A] with Selective.AllOps[F, A] { + trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { type TypeClassType <: RigidSelective[F] } trait ToRigidSelectiveOps extends Serializable { diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala deleted file mode 100644 index f4c335f482..0000000000 --- a/core/src/main/scala/cats/Selective.scala +++ /dev/null @@ -1,53 +0,0 @@ -package cats - -import simulacrum.{typeclass} -import scala.annotation.implicitNotFound - -@implicitNotFound("Could not find an instance of Selective for ${F}") -@typeclass trait Selective[F[_]] extends Applicative[F] {} - -object Selective { - /* ======================================================================== */ - /* THE FOLLOWING CODE IS MANAGED BY SIMULACRUM; PLEASE DO NOT EDIT!!!! */ - /* ======================================================================== */ - - /** - * Summon an instance of [[Selective]] for `F`. - */ - @inline def apply[F[_]](implicit instance: Selective[F]): Selective[F] = instance - - @deprecated("Use cats.syntax object imports", "2.2.0") - object ops { - implicit def toAllSelectiveOps[F[_], A](target: F[A])(implicit tc: Selective[F]): AllOps[F, A] { - type TypeClassType = Selective[F] - } = new AllOps[F, A] { - type TypeClassType = Selective[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } - } - trait Ops[F[_], A] extends Serializable { - type TypeClassType <: Selective[F] - def self: F[A] - val typeClassInstance: TypeClassType - } - trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { - type TypeClassType <: Selective[F] - } - trait ToSelectiveOps extends Serializable { - implicit def toSelectiveOps[F[_], A](target: F[A])(implicit tc: Selective[F]): Ops[F, A] { - type TypeClassType = Selective[F] - } = new Ops[F, A] { - type TypeClassType = Selective[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } - } - @deprecated("Use cats.syntax object imports", "2.2.0") - object nonInheritedOps extends ToSelectiveOps - - /* ======================================================================== */ - /* END OF SIMULACRUM-MANAGED CODE */ - /* ======================================================================== */ - -} diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index fd09899027..b02cdf7bf6 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -933,7 +933,7 @@ sealed abstract private[data] class ValidatedInstances extends ValidatedInstance implicit def catsDataSelectiveErrorForValidated[E](implicit E: Semigroup[E] - ): Selective[Validated[E, *]] with ApplicativeError[Validated[E, *], E] = + ): ApplicativeError[Validated[E, *], E] = new ValidatedSelective[E] with ApplicativeError[Validated[E, *], E] { def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = fa match { @@ -959,7 +959,7 @@ sealed abstract private[data] class ValidatedInstances1 extends ValidatedInstanc } implicit def catsDataCommutativeSelectiveForValidated[E: CommutativeSemigroup] - : Selective[Validated[E, *]] with CommutativeApplicative[Validated[E, *]] = + : CommutativeApplicative[Validated[E, *]] = new ValidatedSelective[E] with CommutativeApplicative[Validated[E, *]] @deprecated("Use catsDataCommutativeSelectiveForValidated", "2.4.0") @@ -1044,7 +1044,7 @@ sealed abstract private[data] class ValidatedInstances2 { // scalastyle:off method.length } -private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] with Selective[Validated[E, *]] { +private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] { override def select[A, B](fab: Validated[E, Either[A, B]])(ff: => Validated[E, A => B]): Validated[E, B] = fab match { case Valid(Left(a)) => ff.map(_(a)) diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 98ba5eac96..f9e6e2fa39 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -51,7 +51,6 @@ trait AllSyntax with PartialOrderSyntax with ProfunctorSyntax with ReducibleSyntax - with SelectiveSyntax with SemigroupSyntax with SemigroupKSyntax with ShowSyntax diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala deleted file mode 100644 index 77fa8d994d..0000000000 --- a/core/src/main/scala/cats/syntax/selective.scala +++ /dev/null @@ -1,4 +0,0 @@ -package cats -package syntax - -trait SelectiveSyntax extends Selective.ToSelectiveOps diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index 5a311c1ea1..2efcb1697b 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -6,8 +6,8 @@ import cats.syntax.all._ /** * Laws that must be obeyed by any rigid `Selective`. */ -trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { - implicit override def F: Selective[F] +trait RigidSelectiveLaws[F[_]] extends ApplicativeLaws[F] { + implicit override def F: Applicative[F] def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = ff.ap(fa) <-> { @@ -38,6 +38,6 @@ trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { } object RigidSelectiveLaws { - def apply[F[_]](implicit ev: Selective[F]): RigidSelectiveLaws[F] = - new RigidSelectiveLaws[F] { def F: Selective[F] = ev } + def apply[F[_]](implicit ev: RigidSelective[F]): RigidSelectiveLaws[F] = + new RigidSelectiveLaws[F] { def F: RigidSelective[F] = ev } } diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala deleted file mode 100644 index 4d44118d36..0000000000 --- a/laws/src/main/scala/cats/laws/SelectiveLaws.scala +++ /dev/null @@ -1,14 +0,0 @@ -package cats -package laws - -/** - * Laws that must be obeyed by any `Selective`. - */ -trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { - implicit override def F: Selective[F] -} - -object SelectiveLaws { - def apply[F[_]](implicit ev: Selective[F]): SelectiveLaws[F] = - new SelectiveLaws[F] { def F: Selective[F] = ev } -} diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index 6d69908ad8..fbad3aec17 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -6,7 +6,7 @@ import cats.laws.discipline.SemigroupalTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { +trait RigidSelectiveTests[F[_]] extends ApplicativeTests[F] { def laws: RigidSelectiveLaws[F] def rigidSelective[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -39,7 +39,7 @@ trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { new RuleSet { def name: String = "rigidSelective" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(selective[A, B, C]) + def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) def props: Seq[(String, Prop)] = Seq( "selective apply" -> forAll(laws.selectiveApply[A, B] _), diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala deleted file mode 100644 index 7e97892f8a..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ /dev/null @@ -1,54 +0,0 @@ -package cats -package laws -package discipline - -import cats.laws.discipline.SemigroupalTests.Isomorphisms -import org.scalacheck.{Arbitrary, Cogen, Prop} - -trait SelectiveTests[F[_]] extends ApplicativeTests[F] { - def laws: SelectiveLaws[F] - - def selective[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 = { - // Derive implicits required after bincompat was locked in for 2.0 - - implicit val ArbFCond: Arbitrary[F[Boolean]] = Arbitrary(for { - fa <- ArbFA.arbitrary - b <- Arbitrary.arbitrary[Boolean] - } yield laws.F.as(fa, b)) - - implicit val ArbFUnit: Arbitrary[F[Unit]] = Arbitrary(for { - fa <- ArbFA.arbitrary - } yield laws.F.as(fa, ())) - - new RuleSet { - def name: String = "selective" - def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) - def props: Seq[(String, Prop)] = - Seq( - ) - } - } -} - -object SelectiveTests { - def apply[F[_]: Selective]: SelectiveTests[F] = - new SelectiveTests[F] { - def laws: SelectiveLaws[F] = SelectiveLaws[F] - } -} diff --git a/tests/src/test/scala/cats/tests/ValidatedSuite.scala b/tests/src/test/scala/cats/tests/ValidatedSuite.scala index 754c8fb7a0..281afc9c0a 100644 --- a/tests/src/test/scala/cats/tests/ValidatedSuite.scala +++ b/tests/src/test/scala/cats/tests/ValidatedSuite.scala @@ -6,7 +6,6 @@ import cats.{ ApplicativeError, Bitraverse, CommutativeApplicative, - Selective, SemigroupK, Semigroupal, Show, @@ -46,9 +45,6 @@ class ValidatedSuite extends CatsSuite { SerializableTests.serializable(ApplicativeError[Validated[String, *], String]) ) - checkAll("Validated[String, Int]", SelectiveTests[Validated[String, *]].selective[Int, Int, Int]) - checkAll("Selective[Validated[String, *]]", SerializableTests.serializable(Selective[Validated[String, *]])) - checkAll("Validated[String, Int] with Option", TraverseTests[Validated[String, *]].traverse[Int, Int, Int, Int, Option, Option] ) From acce38c9eff789203eb56b280aaa3fba334ae647 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 20 Dec 2020 00:34:11 -0500 Subject: [PATCH 39/46] Remove commented implicits --- .../cats/laws/discipline/ApplyTests.scala | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index 18cf10c76b..c0f19f3325 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -69,45 +69,6 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { ) } } - - // Derived implicits to preserve bincompat - -// implicit protected def derivedArbitraryEither[A, B](implicit -// arbFA: Arbitrary[F[A]], -// arbFB: Arbitrary[F[B]] -// ): Arbitrary[F[Either[A, B]]] = { -// Arbitrary( -// Gen.oneOf(arbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), -// arbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) -// ) -// ) -// } - -// implicit protected def derivedArbitraryFunctionComposition[A, B, C](implicit -// arbFAtoB: Arbitrary[F[A => B]], -// arbFBtoC: Arbitrary[F[B => C]] -// ): Arbitrary[F[A => C]] = -// Arbitrary(for { -// fAToB <- arbFAtoB.arbitrary -// fBToC <- arbFBtoC.arbitrary -// } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) - -// implicit protected def derivedArbitraryAtoBtoC[A, B, C](implicit -// arbFA: Arbitrary[F[A]], -// arbC: Arbitrary[C], -// cogenA: Cogen[A], -// cogenB: Cogen[B] -// ): Arbitrary[F[A => B => C]] = -// Arbitrary(for { -// fa <- arbFA.arbitrary -// f <- Gen.function1(Gen.function1(arbC.arbitrary)(cogenB))(cogenA) -// } yield laws.F.as(fa, f)) - -// implicit def derivedArbitraryFB[A, B](implicit arbFA: Arbitrary[F[A]], arbB: Arbitrary[B]): Arbitrary[F[B]] = -// Arbitrary(for { -// fa <- arbFA.arbitrary -// b <- arbB.arbitrary -// } yield laws.F.as(fa, b)) } object ApplyTests { From 7c78d54b9d52223535baa3335af0b181bda73fb8 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 20 Dec 2020 00:34:45 -0500 Subject: [PATCH 40/46] RigidSelective default ap causes loops --- core/src/main/scala/cats/RigidSelective.scala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/RigidSelective.scala b/core/src/main/scala/cats/RigidSelective.scala index 19e7b0973c..eebb74b43e 100644 --- a/core/src/main/scala/cats/RigidSelective.scala +++ b/core/src/main/scala/cats/RigidSelective.scala @@ -4,13 +4,7 @@ import simulacrum.typeclass import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of RigidSelective for ${F}") -@typeclass trait RigidSelective[F[_]] extends Applicative[F] { - override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = { - val left: F[Either[A => B, B]] = map(ff)(Left(_)) - val right: F[(A => B) => B] = map(fa)((a: A) => _(a)) - select(left)(right) - } -} +@typeclass trait RigidSelective[F[_]] extends Applicative[F] object RigidSelective { /* ======================================================================== */ From c4c144255461848695edd147783733cc2d923fc4 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 20 Dec 2020 00:41:38 -0500 Subject: [PATCH 41/46] Add back lost selective associativity test --- .../scala/cats/laws/discipline/ApplyTests.scala | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index c0f19f3325..e23390b3b6 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -45,6 +45,20 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { ) ) + implicit val ArbFCAtoB: Arbitrary[F[Either[C, A => B]]] = Arbitrary( + Gen.oneOf( + ArbFC.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A => B])), + ArbFAtoB.arbitrary.map(faToB => laws.F.map(faToB)(_.asRight[C])) + ) + ) + + implicit val ArbCtoAtoB: Arbitrary[F[C => A => B]] = Arbitrary( + for { + fa <- ArbFA.arbitrary + f <- Gen.function1(Gen.function1(Arbitrary.arbitrary[B])(CogenA))(CogenC) + } yield laws.F.as(fa, f) + ) + implicit val ArbFAtoC: Arbitrary[F[A => C]] = Arbitrary(for { fAToB <- ArbFAtoB.arbitrary @@ -65,7 +79,8 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _), "branch/select consistency" -> forAll(laws.branchConsistency[A, B, C] _), "ifS/branch consistency" -> forAll(laws.ifSConsistency[A] _), - "selectA consistent map2" -> forAll(laws.selectAConsistency[A, C] _) + "selectA consistent map2" -> forAll(laws.selectAConsistency[A, C] _), + "select associativity" -> forAll(laws.selectAssociativity[A, B, C] _) ) } } From 3bcb79123a04070dfcaa2a732968aaa0b1f114fb Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 20 Dec 2020 01:06:18 -0500 Subject: [PATCH 42/46] Restate ifS skip laws to accommodate Under --- laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index 2efcb1697b..88811cef2b 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -28,10 +28,10 @@ trait RigidSelectiveLaws[F[_]] extends ApplicativeLaws[F] { fb.map(Right(_)).branch(ope[A => C])(fr) <-> fb.map(Left(_)).select(fr) def selectiveIfSSkipFalse[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = - fa.as(true).ifS(fb)(ope[B]) <-> fa *> fb + fa.as(true).ifS(fb)(ope[B]) <-> fa.as(Either.leftUnit).select(fb.map(Function.const)) def selectiveIfSSkipTrue[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = - fa.as(false).ifS(ope[B])(fb) <-> fa *> fb + fa.as(false).ifS(ope[B])(fb) <-> fa.as(Either.leftUnit).select(fb.map(Function.const)) def selectiveWhenSSkip[A](fa: F[A]): IsEq[F[Unit]] = fa.as(false).whenS(ope[Unit]) <-> fa *> F.unit From dba3dc059182786a2860dacfbf9121b93109ae62 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 20 Dec 2020 21:00:58 -0500 Subject: [PATCH 43/46] Selective has re-entered the chat --- core/src/main/scala/cats/Applicative.scala | 6 +- core/src/main/scala/cats/Apply.scala | 30 ----- core/src/main/scala/cats/RigidSelective.scala | 4 +- core/src/main/scala/cats/Selective.scala | 66 +++++++++++ core/src/main/scala/cats/data/Validated.scala | 2 +- core/src/main/scala/cats/syntax/all.scala | 1 + .../main/scala/cats/syntax/applicative.scala | 6 - core/src/main/scala/cats/syntax/apply.scala | 7 -- core/src/main/scala/cats/syntax/package.scala | 1 + .../main/scala/cats/syntax/selective.scala | 4 + .../scala/cats/laws/ApplicativeLaws.scala | 11 -- laws/src/main/scala/cats/laws/ApplyLaws.scala | 34 ------ .../scala/cats/laws/RigidSelectiveLaws.scala | 13 +-- .../main/scala/cats/laws/SelectiveLaws.scala | 41 +++++++ .../laws/discipline/ApplicativeTests.scala | 29 +---- .../cats/laws/discipline/ApplyTests.scala | 52 +-------- .../laws/discipline/RigidSelectiveTests.scala | 9 +- .../cats/laws/discipline/SelectiveTests.scala | 105 ++++++++++++++++++ 18 files changed, 231 insertions(+), 190 deletions(-) create mode 100644 core/src/main/scala/cats/Selective.scala create mode 100644 core/src/main/scala/cats/syntax/selective.scala create mode 100644 laws/src/main/scala/cats/laws/SelectiveLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 9e64e18ab3..09f8fd570f 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -1,7 +1,7 @@ package cats import cats.arrow.Arrow -import simulacrum.{noop, typeclass} +import simulacrum.typeclass import scala.annotation.implicitNotFound /** @@ -185,10 +185,6 @@ import scala.annotation.implicitNotFound */ def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = if (cond) void(f) else unit - - @noop - def whenS[A](fCond: F[Boolean])(fTrue: => F[Unit]): F[Unit] = - ifS(fCond)(fTrue)(unit) } object Applicative { diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index 5c81f81410..8fb175f264 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -257,30 +257,6 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse ap2(map(fcond)(ite))(ifTrue, ifFalse) } - - def select[A, B](fab: F[Either[A, B]])(ff: => F[A => B]): F[B] = - selectA(fab)(ff) - - def selectA[A, B](fab: F[Either[A, B]])(ff: F[A => B]): F[B] = - map2(fab, ff) { - case (Left(a), f) => f(a) - case (Right(b), _) => b - } - - def branch[A, B, C](fab: F[Either[A, B]])(fl: => F[A => C])(fr: => F[B => C]): F[C] = { - val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) - def innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) - val lhs = select(innerLhs)(innerRhs) - select(lhs)(fr) - } - - @noop - def ifS[A](fCond: F[Boolean])(fTrue: => F[A])(fFalse: => F[A]): F[A] = { - val condition: F[Either[Unit, Unit]] = map(fCond)(if (_) EitherUtil.leftUnit else EitherUtil.unit) - def left: F[Unit => A] = map(fTrue)(Function.const) - def right: F[Unit => A] = map(fFalse)(Function.const) - branch(condition)(left)(right) - } } object Apply { @@ -335,12 +311,6 @@ object Apply { typeClassInstance.ap2[B, C, D](self.asInstanceOf[F[(B, C) => D]])(fa, fb) def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] = typeClassInstance.map2[A, B, C](self, fb)(f) def map2Eval[B, C](fb: Eval[F[B]])(f: (A, B) => C): Eval[F[C]] = typeClassInstance.map2Eval[A, B, C](self, fb)(f) - def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = - typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) - def selectA[B, C](ff: F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = - typeClassInstance.selectA[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) - def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = - typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) } trait AllOps[F[_], A] extends Ops[F, A] with Functor.AllOps[F, A] with InvariantSemigroupal.AllOps[F, A] { type TypeClassType <: Apply[F] diff --git a/core/src/main/scala/cats/RigidSelective.scala b/core/src/main/scala/cats/RigidSelective.scala index eebb74b43e..ba7c108af9 100644 --- a/core/src/main/scala/cats/RigidSelective.scala +++ b/core/src/main/scala/cats/RigidSelective.scala @@ -4,7 +4,7 @@ import simulacrum.typeclass import scala.annotation.implicitNotFound @implicitNotFound("Could not find an instance of RigidSelective for ${F}") -@typeclass trait RigidSelective[F[_]] extends Applicative[F] +@typeclass trait RigidSelective[F[_]] extends Selective[F] object RigidSelective { /* ======================================================================== */ @@ -31,7 +31,7 @@ object RigidSelective { def self: F[A] val typeClassInstance: TypeClassType } - trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { + trait AllOps[F[_], A] extends Ops[F, A] with Selective.AllOps[F, A] { type TypeClassType <: RigidSelective[F] } trait ToRigidSelectiveOps extends Serializable { diff --git a/core/src/main/scala/cats/Selective.scala b/core/src/main/scala/cats/Selective.scala new file mode 100644 index 0000000000..133289d2dd --- /dev/null +++ b/core/src/main/scala/cats/Selective.scala @@ -0,0 +1,66 @@ +package cats + +import simulacrum.typeclass +import scala.annotation.implicitNotFound + +@implicitNotFound("Could not find an instance of Selective for ${F}") +@typeclass trait Selective[F[_]] extends Applicative[F] { + def select[A, B](fab: F[Either[A, B]])(ff: => F[A => B]): F[B] + + def branch[A, B, C](fab: F[Either[A, B]])(fl: => F[A => C])(fr: => F[B => C]): F[C] = { + val innerLhs: F[Either[A, Either[B, C]]] = map(fab)(_.map(Left(_))) + def innerRhs: F[A => Either[B, C]] = map(fl)(_.andThen(Right(_))) + val lhs = select(innerLhs)(innerRhs) + select(lhs)(fr) + } +} + +object Selective { + /* ======================================================================== */ + /* THE FOLLOWING CODE IS MANAGED BY SIMULACRUM; PLEASE DO NOT EDIT!!!! */ + /* ======================================================================== */ + + /** + * Summon an instance of [[Selective]] for `F`. + */ + @inline def apply[F[_]](implicit instance: Selective[F]): Selective[F] = instance + + @deprecated("Use cats.syntax object imports", "2.2.0") + object ops { + implicit def toAllSelectiveOps[F[_], A](target: F[A])(implicit tc: Selective[F]): AllOps[F, A] { + type TypeClassType = Selective[F] + } = new AllOps[F, A] { + type TypeClassType = Selective[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } + } + trait Ops[F[_], A] extends Serializable { + type TypeClassType <: Selective[F] + def self: F[A] + val typeClassInstance: TypeClassType + def select[B, C](ff: => F[B => C])(implicit ev$1: A <:< Either[B, C]): F[C] = + typeClassInstance.select[B, C](self.asInstanceOf[F[Either[B, C]]])(ff) + def branch[B, C, D](fl: => F[B => D])(fr: => F[C => D])(implicit ev$1: A <:< Either[B, C]): F[D] = + typeClassInstance.branch[B, C, D](self.asInstanceOf[F[Either[B, C]]])(fl)(fr) + } + trait AllOps[F[_], A] extends Ops[F, A] with Applicative.AllOps[F, A] { + type TypeClassType <: Selective[F] + } + trait ToSelectiveOps extends Serializable { + implicit def toSelectiveOps[F[_], A](target: F[A])(implicit tc: Selective[F]): Ops[F, A] { + type TypeClassType = Selective[F] + } = new Ops[F, A] { + type TypeClassType = Selective[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } + } + @deprecated("Use cats.syntax object imports", "2.2.0") + object nonInheritedOps extends ToSelectiveOps + + /* ======================================================================== */ + /* END OF SIMULACRUM-MANAGED CODE */ + /* ======================================================================== */ + +} diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index b02cdf7bf6..65ef22c9ca 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -1044,7 +1044,7 @@ sealed abstract private[data] class ValidatedInstances2 { // scalastyle:off method.length } -private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] { +private[data] class ValidatedSelective[E: Semigroup] extends ValidatedApplicative[E] with Selective[Validated[E, *]] { override def select[A, B](fab: Validated[E, Either[A, B]])(ff: => Validated[E, A => B]): Validated[E, B] = fab match { case Valid(Left(a)) => ff.map(_(a)) diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index f9e6e2fa39..98ba5eac96 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -51,6 +51,7 @@ trait AllSyntax with PartialOrderSyntax with ProfunctorSyntax with ReducibleSyntax + with SelectiveSyntax with SemigroupSyntax with SemigroupKSyntax with ShowSyntax diff --git a/core/src/main/scala/cats/syntax/applicative.scala b/core/src/main/scala/cats/syntax/applicative.scala index b010b3fab8..4e2a271b02 100644 --- a/core/src/main/scala/cats/syntax/applicative.scala +++ b/core/src/main/scala/cats/syntax/applicative.scala @@ -6,8 +6,6 @@ trait ApplicativeSyntax { new ApplicativeIdOps[A](a) implicit final def catsSyntaxApplicative[F[_], A](fa: F[A]): ApplicativeOps[F, A] = new ApplicativeOps[F, A](fa) - implicit final def catsSyntaxApplicativeBoolean[F[_]](fBool: F[Boolean]): ApplicativeBooleanOps[F] = - new ApplicativeBooleanOps(fBool) } final class ApplicativeIdOps[A](private val a: A) extends AnyVal { @@ -19,7 +17,3 @@ final class ApplicativeOps[F[_], A](private val fa: F[A]) extends AnyVal { def unlessA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.unlessA(cond)(fa) def whenA(cond: Boolean)(implicit F: Applicative[F]): F[Unit] = F.whenA(cond)(fa) } - -final class ApplicativeBooleanOps[F[_]](private val fCond: F[Boolean]) extends AnyVal { - def whenS[A](fTrue: => F[Unit])(implicit F: Applicative[F]): F[Unit] = F.whenS(fCond)(fTrue) -} diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index 0d9f6dd89a..5966eb3836 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -12,9 +12,6 @@ trait ApplySyntax extends TupleSemigroupalSyntax { implicit final def catsSyntaxApplyOps[F[_], A](fa: F[A]): ApplyOps[F, A] = new ApplyOps(fa) - - implicit final def catsSyntaxApplyBooleanOps[F[_]](fBool: F[Boolean]): ApplyBooleanOps[F] = - new ApplyBooleanOps(fBool) } private[syntax] trait ApplySyntaxBinCompat0 { @@ -71,7 +68,3 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { @inline private[syntax] def forEffect[B](fb: F[B])(implicit F: Apply[F]): F[A] = F.productL(fa)(fb) } - -final class ApplyBooleanOps[F[_]](private val fCond: F[Boolean]) extends AnyVal { - def ifS[A](fTrue: => F[A])(fFalse: => F[A])(implicit F: Apply[F]): F[A] = F.ifS(fCond)(fTrue)(fFalse) -} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 71ed3c2591..bcaa514365 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -54,6 +54,7 @@ package object syntax { object profunctor extends ProfunctorSyntax object reducible extends ReducibleSyntax with ReducibleSyntaxBinCompat0 object representable extends RepresentableSyntax + object selective extends SelectiveSyntax object semigroup extends SemigroupSyntax object semigroupal extends SemigroupalSyntax object semigroupk extends SemigroupKSyntax diff --git a/core/src/main/scala/cats/syntax/selective.scala b/core/src/main/scala/cats/syntax/selective.scala new file mode 100644 index 0000000000..77fa8d994d --- /dev/null +++ b/core/src/main/scala/cats/syntax/selective.scala @@ -0,0 +1,4 @@ +package cats +package syntax + +trait SelectiveSyntax extends Selective.ToSelectiveOps diff --git a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala index 4424e8e1ad..a1405f09a0 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala @@ -2,7 +2,6 @@ package cats package laws import cats.syntax.apply._ -import cats.syntax.applicative._ import cats.syntax.functor._ /** @@ -47,16 +46,6 @@ trait ApplicativeLaws[F[_]] extends ApplyLaws[F] { def monoidalRightIdentity[A](fa: F[A]): (F[(A, Unit)], F[A]) = (F.product(fa, F.pure(())), fa) - - def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = - faa.select[A, A](F.pure(identity)) <-> faa.map(_.merge) - - def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = - F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) - - def whenSIfSConsistency[A](fb: F[Boolean], fa: F[Unit]): IsEq[F[Unit]] = { - fb.whenS(fa) <-> fb.ifS(fa)(F.unit) - } } object ApplicativeLaws { diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index 7175a13b94..0d96699131 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -2,7 +2,6 @@ package cats package laws import cats.syntax.apply._ -import cats.syntax.either._ import cats.syntax.functor._ /** @@ -27,39 +26,6 @@ trait ApplyLaws[F[_]] extends FunctorLaws[F] with SemigroupalLaws[F] { def productLConsistency[A, B](fa: F[A], fb: F[B]): IsEq[F[A]] = F.productL(fa)(fb) <-> F.map2(fa, fb)((a, _) => a) - - def selectAssociativity[A, B, C](fa: F[Either[A, B]], fb: F[Either[C, A => B]], fc: F[C => A => B]): IsEq[F[B]] = { - val fa0 = fa.map(_.map(_.asRight[(C, A)])) - val fb0 = fb.map { either => (a: A) => either.bimap(c => (c, a), f => f(a)) } - val fc0 = fc.map(Function.uncurried(_).tupled) - fa.select(fb.select(fc)) <-> fa0.select(fb0).select(fc0) - } - - def branchConsistency[A, B, C](fab: F[Either[A, B]], fl: F[A => C], fr: F[B => C]): IsEq[F[C]] = { - fab.branch(fl)(fr) <-> { - val lhs = { - val innerLhs: F[Either[A, Either[B, C]]] = F.map(fab)(_.map(Left(_))) - val innerRhs: F[A => Either[B, C]] = F.map(fl)(_.andThen(Right(_))) - F.select(innerLhs)(innerRhs) - } - F.select(lhs)(fr) - } - } - - def ifSConsistency[A](fb: F[Boolean], ft: F[A], ff: F[A]): IsEq[F[A]] = { - fb.ifS(ft)(ff) <-> { - val condition: F[Either[Unit, Unit]] = F.map(fb)(p => if (p) Left(()) else Right(())) - val left: F[Unit => A] = F.map(ft)(Function.const) - val right: F[Unit => A] = F.map(ff)(Function.const) - F.branch(condition)(left)(right) - } - } - - def selectAConsistency[A, B](fab: F[Either[A, B]], ff: F[A => B]): IsEq[F[B]] = - F.selectA(fab)(ff) <-> F.map2(fab, ff) { - case (Left(a), f) => f(a) - case (Right(b), _) => b - } } object ApplyLaws { diff --git a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala index 88811cef2b..a24df25aaa 100644 --- a/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -6,8 +6,8 @@ import cats.syntax.all._ /** * Laws that must be obeyed by any rigid `Selective`. */ -trait RigidSelectiveLaws[F[_]] extends ApplicativeLaws[F] { - implicit override def F: Applicative[F] +trait RigidSelectiveLaws[F[_]] extends SelectiveLaws[F] { + implicit override def F: Selective[F] def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = ff.ap(fa) <-> { @@ -26,15 +26,6 @@ trait RigidSelectiveLaws[F[_]] extends ApplicativeLaws[F] { def selectiveBranchSkipLeft[A, B, C](fb: F[B], fr: F[B => C]): IsEq[F[C]] = fb.map(Right(_)).branch(ope[A => C])(fr) <-> fb.map(Left(_)).select(fr) - - def selectiveIfSSkipFalse[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = - fa.as(true).ifS(fb)(ope[B]) <-> fa.as(Either.leftUnit).select(fb.map(Function.const)) - - def selectiveIfSSkipTrue[A, B](fa: F[A], fb: F[B]): IsEq[F[B]] = - fa.as(false).ifS(ope[B])(fb) <-> fa.as(Either.leftUnit).select(fb.map(Function.const)) - - def selectiveWhenSSkip[A](fa: F[A]): IsEq[F[Unit]] = - fa.as(false).whenS(ope[Unit]) <-> fa *> F.unit } object RigidSelectiveLaws { diff --git a/laws/src/main/scala/cats/laws/SelectiveLaws.scala b/laws/src/main/scala/cats/laws/SelectiveLaws.scala new file mode 100644 index 0000000000..78c7adfca5 --- /dev/null +++ b/laws/src/main/scala/cats/laws/SelectiveLaws.scala @@ -0,0 +1,41 @@ +package cats +package laws + +import cats.syntax.apply._ +import cats.syntax.either._ +import cats.syntax.functor._ +import cats.syntax.selective._ + +/** + * Laws that must be obeyed by any `Selective`. + */ +trait SelectiveLaws[F[_]] extends ApplicativeLaws[F] { + implicit override def F: Selective[F] + + def selectiveIdentity[A, B](faa: F[Either[A, A]]): IsEq[F[A]] = + faa.select[A, A](F.pure(identity)) <-> faa.map(_.merge) + + def selectiveDistributivity[A, B](ab: Either[A, B], ff1: F[A => B], ff2: F[A => B]): IsEq[F[B]] = + F.pure(ab).select(ff1 *> ff2) <-> F.pure(ab).select(ff1) *> F.pure(ab).select(ff2) + + def selectAssociativity[A, B, C](fa: F[Either[A, B]], fb: F[Either[C, A => B]], fc: F[C => A => B]): IsEq[F[B]] = { + val fa0 = fa.map(_.map(_.asRight[(C, A)])) + val fb0 = fb.map { either => (a: A) => either.bimap(c => (c, a), f => f(a)) } + val fc0 = fc.map(Function.uncurried(_).tupled) + fa.select(fb.select(fc)) <-> fa0.select(fb0).select(fc0) + } + + def branchSelectConsistency[A, B, C](fab: F[Either[A, B]], fl: F[A => C], fr: F[B => C]): IsEq[F[C]] = { + fab.branch(fl)(fr) <-> { + val innerLhs: F[Either[A, Either[B, C]]] = F.map(fab)(_.map(Left(_))) + val innerRhs: F[A => Either[B, C]] = F.map(fl)(_.andThen(Right(_))) + val lhs = F.select(innerLhs)(innerRhs) + F.select(lhs)(fr) + } + } +} + +object SelectiveLaws { + def apply[F[_]](implicit ev: Selective[F]): SelectiveLaws[F] = + new SelectiveLaws[F] { def F: Selective[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala index 18ca3e8acf..08335fd6b7 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala @@ -3,8 +3,7 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import cats.syntax.either._ -import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} +import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ trait ApplicativeTests[F[_]] extends ApplyTests[F] { @@ -25,27 +24,6 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] ): RuleSet = { - implicit val ArbFCond: Arbitrary[F[Boolean]] = Arbitrary(for { - fa <- ArbFA.arbitrary - b <- Arbitrary.arbitrary[Boolean] - } yield laws.F.as(fa, b)) - - implicit val ArbFUnit: Arbitrary[F[Unit]] = Arbitrary(for { - fa <- ArbFA.arbitrary - } yield laws.F.as(fa, ())) - - implicit val ArbFAA: Arbitrary[F[Either[A, A]]] = Arbitrary( - Gen.oneOf( - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) - ) - ) - - implicit val EqFUnit: Eq[F[Unit]] = { - val a = Arbitrary.arbitrary[A].retryUntil(_ => true).sample.get - Eq.by(laws.F.map(_)(_ => a)) - } - new DefaultRuleSet( name = "applicative", parent = Some(apply[A, B, C]), @@ -56,10 +34,7 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { "applicative unit" -> forAll(laws.applicativeUnit[A] _), "ap consistent with product + map" -> forAll(laws.apProductConsistent[A, B] _), "monoidal left identity" -> forAll((fa: F[A]) => iso.leftIdentity(laws.monoidalLeftIdentity(fa))), - "monoidal right identity" -> forAll((fa: F[A]) => iso.rightIdentity(laws.monoidalRightIdentity(fa))), - "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), - "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _), - "whenS/ifS consistency" -> forAll(laws.whenSIfSConsistency[A] _) + "monoidal right identity" -> forAll((fa: F[A]) => iso.rightIdentity(laws.monoidalRightIdentity(fa))) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index e23390b3b6..1ce911fce1 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -3,8 +3,7 @@ package laws package discipline import cats.laws.discipline.SemigroupalTests.Isomorphisms -import cats.syntax.all._ -import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} +import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { @@ -24,49 +23,6 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] ): RuleSet = { - // Derive implicits required after bincompat was locked in for 2.0 - - implicit val ArbFCond: Arbitrary[F[Boolean]] = Arbitrary(for { - fa <- ArbFA.arbitrary - b <- Arbitrary.arbitrary[Boolean] - } yield laws.F.as(fa, b)) - - implicit val ArbFAB: Arbitrary[F[Either[A, B]]] = Arbitrary( - Gen.oneOf( - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), - ArbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) - ) - ) - - implicit val ArbFAC: Arbitrary[F[Either[A, C]]] = Arbitrary( - Gen.oneOf( - ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[C])), - ArbFC.arbitrary.map(fc => laws.F.map(fc)(_.asRight[A])) - ) - ) - - implicit val ArbFCAtoB: Arbitrary[F[Either[C, A => B]]] = Arbitrary( - Gen.oneOf( - ArbFC.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A => B])), - ArbFAtoB.arbitrary.map(faToB => laws.F.map(faToB)(_.asRight[C])) - ) - ) - - implicit val ArbCtoAtoB: Arbitrary[F[C => A => B]] = Arbitrary( - for { - fa <- ArbFA.arbitrary - f <- Gen.function1(Gen.function1(Arbitrary.arbitrary[B])(CogenA))(CogenC) - } yield laws.F.as(fa, f) - ) - - implicit val ArbFAtoC: Arbitrary[F[A => C]] = - Arbitrary(for { - fAToB <- ArbFAtoB.arbitrary - fBToC <- ArbFBtoC.arbitrary - } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) - - implicit val EqFB: Eq[F[B]] = Eq.by((fb: F[B]) => laws.F.map(fb)((null.asInstanceOf[A], _, null.asInstanceOf[C]))) - new RuleSet { val name = "apply" val parents = Seq(functor[A, B, C], semigroupal[A, B, C]) @@ -76,11 +32,7 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { "map2/product-map consistency" -> forAll(laws.map2ProductConsistency[A, B, C] _), "map2/map2Eval consistency" -> forAll(laws.map2EvalConsistency[A, B, C] _), "productR consistent map2" -> forAll(laws.productRConsistency[A, C] _), - "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _), - "branch/select consistency" -> forAll(laws.branchConsistency[A, B, C] _), - "ifS/branch consistency" -> forAll(laws.ifSConsistency[A] _), - "selectA consistent map2" -> forAll(laws.selectAConsistency[A, C] _), - "select associativity" -> forAll(laws.selectAssociativity[A, B, C] _) + "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala index fbad3aec17..730aeebc52 100644 --- a/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -6,7 +6,7 @@ import cats.laws.discipline.SemigroupalTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait RigidSelectiveTests[F[_]] extends ApplicativeTests[F] { +trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { def laws: RigidSelectiveLaws[F] def rigidSelective[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -39,16 +39,13 @@ trait RigidSelectiveTests[F[_]] extends ApplicativeTests[F] { new RuleSet { def name: String = "rigidSelective" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) + def parents: Seq[RuleSet] = Seq(selective[A, B, C]) def props: Seq[(String, Prop)] = Seq( "selective apply" -> forAll(laws.selectiveApply[A, B] _), "selective select skip" -> forAll(laws.selectiveSelectSkip[A, B] _), "selective branch skip right" -> forAll(laws.selectiveBranchSkipRight[A, B, C] _), - "selective branch skip left" -> forAll(laws.selectiveBranchSkipLeft[A, B, C] _), - "selective ifS skip true" -> forAll(laws.selectiveIfSSkipTrue[A, B] _), - "selective ifS skip false" -> forAll(laws.selectiveIfSSkipFalse[A, B] _), - "selective whenS skip" -> forAll(laws.selectiveWhenSSkip[A] _) + "selective branch skip left" -> forAll(laws.selectiveBranchSkipLeft[A, B, C] _) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala new file mode 100644 index 0000000000..c0155c0bec --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -0,0 +1,105 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.SemigroupalTests.Isomorphisms +import cats.syntax.either._ +import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} +import Prop._ + +trait SelectiveTests[F[_]] extends ApplicativeTests[F] { + def laws: SelectiveLaws[F] + + def selective[A: Arbitrary, B: Arbitrary, C: Arbitrary](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)]], + iso: Isomorphisms[F] + ): RuleSet = { + // Derive implicits required after bincompat was locked in for 2.0 + + implicit val ArbFCond: Arbitrary[F[Boolean]] = Arbitrary(for { + fa <- ArbFA.arbitrary + b <- Arbitrary.arbitrary[Boolean] + } yield laws.F.as(fa, b)) + + implicit val ArbFAA: Arbitrary[F[Either[A, A]]] = Arbitrary( + Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A])), + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asRight[A])) + ) + ) + + implicit val ArbFAB: Arbitrary[F[Either[A, B]]] = Arbitrary( + Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[B])), + ArbFB.arbitrary.map(fb => laws.F.map(fb)(_.asRight[A])) + ) + ) + + implicit val ArbFAC: Arbitrary[F[Either[A, C]]] = Arbitrary( + Gen.oneOf( + ArbFA.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[C])), + ArbFC.arbitrary.map(fc => laws.F.map(fc)(_.asRight[A])) + ) + ) + + implicit val ArbFCAtoB: Arbitrary[F[Either[C, A => B]]] = Arbitrary( + Gen.oneOf( + ArbFC.arbitrary.map(fa => laws.F.map(fa)(_.asLeft[A => B])), + ArbFAtoB.arbitrary.map(faToB => laws.F.map(faToB)(_.asRight[C])) + ) + ) + + implicit val ArbCtoAtoB: Arbitrary[F[C => A => B]] = Arbitrary( + for { + fa <- ArbFA.arbitrary + f <- Gen.function1(Gen.function1(Arbitrary.arbitrary[B])(CogenA))(CogenC) + } yield laws.F.as(fa, f) + ) + + implicit val ArbFAtoC: Arbitrary[F[A => C]] = + Arbitrary(for { + fAToB <- ArbFAtoB.arbitrary + fBToC <- ArbFBtoC.arbitrary + } yield laws.F.map2(fAToB, fBToC)(_ andThen _)) + + implicit val EqFB: Eq[F[B]] = Eq.by((fb: F[B]) => laws.F.map(fb)((null.asInstanceOf[A], _, null.asInstanceOf[C]))) + + implicit val EqFUnit: Eq[F[Unit]] = { + val a = Arbitrary.arbitrary[A].retryUntil(_ => true).sample.get + Eq.by(laws.F.map(_)(_ => a)) + } + + new DefaultRuleSet( + name = "applicative", + parent = Some(apply[A, B, C]), + "applicative identity" -> forAll(laws.applicativeIdentity[A] _), + "applicative homomorphism" -> forAll(laws.applicativeHomomorphism[A, B] _), + "applicative interchange" -> forAll(laws.applicativeInterchange[A, B] _), + "applicative map" -> forAll(laws.applicativeMap[A, B] _), + "applicative unit" -> forAll(laws.applicativeUnit[A] _), + "ap consistent with product + map" -> forAll(laws.apProductConsistent[A, B] _), + "monoidal left identity" -> forAll((fa: F[A]) => iso.leftIdentity(laws.monoidalLeftIdentity(fa))), + "monoidal right identity" -> forAll((fa: F[A]) => iso.rightIdentity(laws.monoidalRightIdentity(fa))), + "selective identity" -> forAll(laws.selectiveIdentity[A, B] _), + "selective distributivity" -> forAll(laws.selectiveDistributivity[A, B] _), + "select associativity" -> forAll(laws.selectAssociativity[A, B, C] _), + "branch-select consistency" -> forAll(laws.branchSelectConsistency[A, B, C] _) + ) + } +} + +object SelectiveTests { + def apply[F[_]: Selective]: SelectiveTests[F] = + new SelectiveTests[F] { def laws: SelectiveLaws[F] = SelectiveLaws[F] } +} From 7a4d66a23207975671beb09881937e4ab8363e07 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 20 Dec 2020 21:50:57 -0500 Subject: [PATCH 44/46] Selective[ZipLazyList] --- core/src/main/scala-2.13+/cats/data/ZipLazyList.scala | 11 +++++++++-- .../test/scala-2.13+/cats/tests/LazyListSuite.scala | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala-2.13+/cats/data/ZipLazyList.scala b/core/src/main/scala-2.13+/cats/data/ZipLazyList.scala index 20c4832560..9c9be41513 100644 --- a/core/src/main/scala-2.13+/cats/data/ZipLazyList.scala +++ b/core/src/main/scala-2.13+/cats/data/ZipLazyList.scala @@ -7,8 +7,9 @@ object ZipLazyList { def apply[A](value: LazyList[A]): ZipLazyList[A] = new ZipLazyList(value) - implicit val catsDataAlternativeForZipLazyList: Alternative[ZipLazyList] with CommutativeApplicative[ZipLazyList] = - new Alternative[ZipLazyList] with CommutativeApplicative[ZipLazyList] { + implicit val catsDataAlternativeForZipLazyList + : Alternative[ZipLazyList] with CommutativeApplicative[ZipLazyList] with Selective[ZipLazyList] = + new Alternative[ZipLazyList] with CommutativeApplicative[ZipLazyList] with Selective[ZipLazyList] { def pure[A](x: A): ZipLazyList[A] = new ZipLazyList(LazyList.continually(x)) override def map[A, B](fa: ZipLazyList[A])(f: (A) => B): ZipLazyList[B] = @@ -20,6 +21,12 @@ object ZipLazyList { override def product[A, B](fa: ZipLazyList[A], fb: ZipLazyList[B]): ZipLazyList[(A, B)] = ZipLazyList(fa.value.zip(fb.value)) + def select[A, B](fab: ZipLazyList[Either[A, B]])(ff: => ZipLazyList[A => B]): ZipLazyList[B] = + ZipLazyList(fab.value.lazyZip(ff.value).map { + case (Left(a), f) => f(a) + case (Right(b), _) => b + }) + def empty[A]: ZipLazyList[A] = ZipLazyList(LazyList.empty[A]) def combineK[A](x: ZipLazyList[A], y: ZipLazyList[A]): ZipLazyList[A] = diff --git a/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala b/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala index 6a5365ab8a..f04be74e2a 100644 --- a/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala +++ b/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala @@ -47,7 +47,7 @@ class LazyListSuite extends CatsSuite { checkAll("LazyList[Int]", ShortCircuitingTests[LazyList].foldable[Int]) checkAll("LazyList[Int]", ShortCircuitingTests[LazyList].traverseFilter[Int]) - // Can't test applicative laws as they don't terminate + // Can't test applicative or selective laws as they don't terminate checkAll("ZipLazyList[Int]", CommutativeApplyTests[ZipLazyList].apply[Int, Int, Int]) test("show") { From 6616707cd529b8788006dab8292bbabae57128bd Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Mon, 21 Dec 2020 01:43:42 -0500 Subject: [PATCH 45/46] Selective[Func] and RigidSelective[Func] --- core/src/main/scala/cats/data/Func.scala | 32 +++++++++++++++++-- .../src/test/scala/cats/tests/FuncSuite.scala | 18 ++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala index 0961864b38..79c2c2b1da 100644 --- a/core/src/main/scala/cats/data/Func.scala +++ b/core/src/main/scala/cats/data/Func.scala @@ -42,20 +42,36 @@ object Func extends FuncInstances { } abstract private[data] class FuncInstances extends FuncInstances0 { + implicit def catsDataRigidSelectiveForFunc[F[_], C](implicit + FF: RigidSelective[F] + ): RigidSelective[λ[α => Func[F, C, α]]] = + new FuncRigidSelective[F, C] { + def F: RigidSelective[F] = FF + } +} + +abstract private[data] class FuncInstances0 extends FuncInstances1 { + implicit def catsDataSelectiveForFunc[F[_], C](implicit FF: Selective[F]): Selective[λ[α => Func[F, C, α]]] = + new FuncSelective[F, C] { + def F: Selective[F] = FF + } +} + +abstract private[data] class FuncInstances1 extends FuncInstances2 { implicit def catsDataApplicativeForFunc[F[_], C](implicit FF: Applicative[F]): Applicative[λ[α => Func[F, C, α]]] = new FuncApplicative[F, C] { def F: Applicative[F] = FF } } -abstract private[data] class FuncInstances0 extends FuncInstances1 { +abstract private[data] class FuncInstances2 extends FuncInstances3 { implicit def catsDataApplyForFunc[F[_], C](implicit FF: Apply[F]): Apply[λ[α => Func[F, C, α]]] = new FuncApply[F, C] { def F: Apply[F] = FF } } -abstract private[data] class FuncInstances1 { +abstract private[data] class FuncInstances3 { implicit def catsDataFunctorForFunc[F[_], C](implicit FF: Functor[F]): Functor[λ[α => Func[F, C, α]]] = new FuncFunctor[F, C] { def F: Functor[F] = FF @@ -95,6 +111,18 @@ sealed private[data] trait FuncApplicative[F[_], C] extends Applicative[λ[α => Func.func(c => F.pure(a)) } +sealed private[data] trait FuncSelective[F[_], C] extends Selective[λ[α => Func[F, C, α]]] with FuncApplicative[F, C] { + def F: Selective[F] + def select[A, B](fab: Func[F, C, Either[A, B]])(ff: => Func[F, C, A => B]): Func[F, C, B] = + Func.func(c => F.select(fab.run(c))(ff.run(c))) +} + +sealed private[data] trait FuncRigidSelective[F[_], C] + extends RigidSelective[λ[α => Func[F, C, α]]] + with FuncSelective[F, C] { + def F: RigidSelective[F] +} + /** * An implementation of [[Func]] that's specialized to [[Applicative]]. */ diff --git a/tests/src/test/scala/cats/tests/FuncSuite.scala b/tests/src/test/scala/cats/tests/FuncSuite.scala index 4f58d15454..e3bf00fe05 100644 --- a/tests/src/test/scala/cats/tests/FuncSuite.scala +++ b/tests/src/test/scala/cats/tests/FuncSuite.scala @@ -1,6 +1,6 @@ package cats.tests -import cats.{Applicative, Apply, Contravariant, Functor, Semigroupal, Show} +import cats.{Applicative, Apply, Contravariant, Functor, RigidSelective, Selective, Semigroupal, Show} import cats.data.{AppFunc, Func} import cats.data.Func.appFunc import cats.kernel.Eq @@ -28,6 +28,22 @@ class FuncSuite extends CatsSuite { checkAll("Applicative[Func[Option, Int, *]]", SerializableTests.serializable(Applicative[Func[Option, Int, *]])) } + { + implicit val catsDataSelectiveForFunc: Selective[Func[Option, Int, *]] = + Func.catsDataRigidSelectiveForFunc[Option, Int] + checkAll("Func[Option, MiniInt, Int]", SelectiveTests[Func[Option, MiniInt, *]].selective[Int, Int, Int]) + checkAll("Selective[Func[Option, Int, *]]", SerializableTests.serializable(Selective[Func[Option, Int, *]])) + } + + { + implicit val catsDataRigidSelectiveForFunc: RigidSelective[Func[Option, Int, *]] = + Func.catsDataRigidSelectiveForFunc[Option, Int] + checkAll("Func[Option, MiniInt, Int]", RigidSelectiveTests[Func[Option, MiniInt, *]].rigidSelective[Int, Int, Int]) + checkAll("RigidSelective[Func[Option, Int, *]]", + SerializableTests.serializable(RigidSelective[Func[Option, Int, *]]) + ) + } + { implicit val catsDataApplyForFunc: Apply[Func[Option, MiniInt, *]] = Func.catsDataApplyForFunc[Option, MiniInt] checkAll("Func[Option, MiniInt, Int]", ApplyTests[Func[Option, MiniInt, *]].apply[Int, Int, Int]) From bd0d4d9c11525dbc4a3fb014072bcb3a9899ef9a Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Mon, 21 Dec 2020 01:44:50 -0500 Subject: [PATCH 46/46] Fix name of selective laws --- laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala index c0155c0bec..ce2211c7cd 100644 --- a/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SelectiveTests.scala @@ -81,7 +81,7 @@ trait SelectiveTests[F[_]] extends ApplicativeTests[F] { } new DefaultRuleSet( - name = "applicative", + name = "selective", parent = Some(apply[A, B, C]), "applicative identity" -> forAll(laws.applicativeIdentity[A] _), "applicative homomorphism" -> forAll(laws.applicativeHomomorphism[A, B] _),