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/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index c57f2d3bce..09f8fd570f 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 { @@ -243,12 +242,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] @@ -261,12 +259,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/Apply.scala b/core/src/main/scala/cats/Apply.scala index fb64a1b93f..8fb175f264 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -289,12 +289,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] @@ -319,12 +318,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/core/src/main/scala/cats/EitherUtil.scala b/core/src/main/scala/cats/EitherUtil.scala new file mode 100644 index 0000000000..8e2a28bfdd --- /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]] + + val unit = Right(()) + val leftUnit = Left(()) +} diff --git a/core/src/main/scala/cats/Monad.scala b/core/src/main/scala/cats/Monad.scala index 131c35addf..ec214c46b2 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 RigidSelective[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. @@ -161,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] @@ -178,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 RigidSelective.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/RigidSelective.scala b/core/src/main/scala/cats/RigidSelective.scala new file mode 100644 index 0000000000..ba7c108af9 --- /dev/null +++ b/core/src/main/scala/cats/RigidSelective.scala @@ -0,0 +1,53 @@ +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] + +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 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/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index fa581de3ac..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 @@ -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/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/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 51100aae05..65ef22c9ca 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -931,9 +931,10 @@ 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] + ): 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 +942,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 +958,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] + implicit def catsDataCommutativeSelectiveForValidated[E: CommutativeSemigroup] : CommutativeApplicative[Validated[E, *]] = - new ValidatedApplicative[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 +1044,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..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 @@ -207,7 +206,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, *] = 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/either.scala b/core/src/main/scala/cats/syntax/either.scala index 534d4117f6..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,7 +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] = cats.EitherUtil.leftUnit } final class LeftOps[A, B](private val left: Left[A, B]) extends AnyVal { @@ -502,11 +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] def unit = cats.EitherUtil.unit + private[cats] def leftUnit = cats.EitherUtil.leftUnit } 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/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala index b488192ae6..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 ApplicativeLaws[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]] = @@ -41,6 +41,15 @@ trait MonadLaws[F[_]] extends ApplicativeLaws[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] + } + fab.select(ff) <-> selectM(fab)(ff) + } } object MonadLaws { 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..a24df25aaa --- /dev/null +++ b/laws/src/main/scala/cats/laws/RigidSelectiveLaws.scala @@ -0,0 +1,34 @@ +package cats +package laws + +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] + + def selectiveApply[A, B](fa: F[A], ff: F[A => B]): IsEq[F[B]] = + 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) + + 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) +} + +object RigidSelectiveLaws { + 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 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 510024fa04..08335fd6b7 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala @@ -23,7 +23,7 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { EqFC: Eq[F[C]], EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] - ): RuleSet = + ): RuleSet = { new DefaultRuleSet( name = "applicative", parent = Some(apply[A, B, C]), @@ -36,6 +36,7 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { "monoidal left identity" -> forAll((fa: F[A]) => iso.leftIdentity(laws.monoidalLeftIdentity(fa))), "monoidal right identity" -> forAll((fa: F[A]) => iso.rightIdentity(laws.monoidalRightIdentity(fa))) ) + } } object ApplicativeTests { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index 57ba7983ab..1ce911fce1 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -22,7 +22,7 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { EqFC: Eq[F[C]], EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] - ): RuleSet = + ): RuleSet = { new RuleSet { val name = "apply" val parents = Seq(functor[A, B, C], semigroupal[A, B, C]) @@ -35,6 +35,7 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] { "productL consistent map2" -> forAll(laws.productLConsistency[A, C] _) ) } + } } object ApplyTests { diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index 82459d34d6..bb28a3c4f3 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 ApplicativeTests[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 @@ -29,7 +29,7 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { 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(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 ApplicativeTests[F] with FlatMapTests[F] { 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(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 new file mode 100644 index 0000000000..730aeebc52 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/RigidSelectiveTests.scala @@ -0,0 +1,59 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.SemigroupalTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait RigidSelectiveTests[F[_]] extends SelectiveTests[F] { + def laws: RigidSelectiveLaws[F] + + def rigidSelective[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 = { + implicit val ArbFAtoC: Arbitrary[F[A => C]] = + Arbitrary(for { + fAToB <- ArbFAtoB.arbitrary + 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 + 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] _) + ) + } + } +} + +object RigidSelectiveTests { + def apply[F[_]: RigidSelective]: RigidSelectiveTests[F] = + new RigidSelectiveTests[F] { + def laws: RigidSelectiveLaws[F] = RigidSelectiveLaws[F] + } +} 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..ce2211c7cd --- /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 = "selective", + 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] } +} 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") { 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])