From e0a572f93d605afe1b901bcae9a4ae7dcf7ec3d3 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 10:52:55 +0100 Subject: [PATCH 01/16] Add ifA to Apply --- core/src/main/scala/cats/Apply.scala | 32 +++++++++++++++++++++ core/src/main/scala/cats/syntax/apply.scala | 5 +--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index 3a43f93a43..e1e3265153 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -214,6 +214,38 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit val G = Apply[G] } + /** + * An `if-then-else` lifted into the `F` context. + * This function combines the effects of the `fcond` condition and of the two branches, + * in the order in which they are given. + * + * The value of the result is, depending on the value of the condition, + * the value of the first argument, or the value of the second argument. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * + * scala> val b1: Option[Boolean] = Some(true) + * scala> val asInt1: Option[Int] = Apply[Option].ifA(b1)(Some(1), Some(0)) + * scala> asInt1.get + * res0: Int = 1 + * + * scala> val b2: Option[Boolean] = Some(false) + * scala> val asInt2: Option[Int] = Apply[Option].ifA(b2)(Some(1), Some(0)) + * scala> asInt2.get + * res1: Int = 0 + * + * scala> val b3: Option[Boolean] = Some(true) + * scala> val asInt3: Option[Int] = Apply[Option].ifA(b3)(Some(1), None) + * asInt2: Option[Int] = None + * + * }}} + */ + def ifA[A](fcond: F[Boolean])(ifTrue: F[A], ifFalse: F[A]): F[A] = { + def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse + ap2(map(fcond)(ite))(ifTrue, ifFalse) + } } object Apply { diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index b11501c15a..3f9f4b7763 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -49,10 +49,7 @@ final class IfApplyOps[F[_]](private val fcond: F[Boolean]) extends AnyVal { * * }}} */ - def ifA[A](ifTrue: F[A], ifFalse: F[A])(implicit F: Apply[F]): F[A] = { - def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse - F.ap2(F.map(fcond)(ite))(ifTrue, ifFalse) - } + def ifA[A](ifTrue: F[A], ifFalse: F[A])(implicit F: Apply[F]): F[A] = F.ifA(fcond)(ifTrue, ifFalse) } final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { From 0655be35904057a541e8e85c4103d70a798584c0 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 11:11:08 +0100 Subject: [PATCH 02/16] Add sequenceFilter to TraverseFilter --- core/src/main/scala/cats/TraverseFilter.scala | 11 +++++++++++ core/src/main/scala/cats/syntax/traverseFilter.scala | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/TraverseFilter.scala b/core/src/main/scala/cats/TraverseFilter.scala index ea2848de63..b4e19c50f5 100644 --- a/core/src/main/scala/cats/TraverseFilter.scala +++ b/core/src/main/scala/cats/TraverseFilter.scala @@ -34,6 +34,17 @@ trait TraverseFilter[F[_]] extends FunctorFilter[F] { */ def traverseFilter[G[_], A, B](fa: F[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[F[B]] + /** + * {{{ + * scala> import cats.implicits._ + * scala> val a: List[Either[String, Option[Int]]] = List(Right(Some(1)), Right(Some(5)), Right(Some(3))) + * scala> val b: Either[String, List[Int]] = TraverseFilter[List].sequenceFilter(a) + * b: Either[String, List[Int]] = Right(List(1, 5, 3)) + * }}} + * */ + def sequenceFilter[G[_], A](fgoa: F[G[Option[A]]])(implicit G: Applicative[G]): G[F[A]] = + traverseFilter(fgoa)(identity) + /** * * Filter values inside a `G` context. diff --git a/core/src/main/scala/cats/syntax/traverseFilter.scala b/core/src/main/scala/cats/syntax/traverseFilter.scala index 9a00baba89..36c57b62a7 100644 --- a/core/src/main/scala/cats/syntax/traverseFilter.scala +++ b/core/src/main/scala/cats/syntax/traverseFilter.scala @@ -18,5 +18,5 @@ final class SequenceFilterOps[F[_], G[_], A](private val fgoa: F[G[Option[A]]]) * b: Either[String, List[Int]] = Right(List(1, 5, 3)) * }}} * */ - def sequenceFilter(implicit F: TraverseFilter[F], G: Applicative[G]): G[F[A]] = F.traverseFilter(fgoa)(identity) + def sequenceFilter(implicit F: TraverseFilter[F], G: Applicative[G]): G[F[A]] = F.sequenceFilter(fgoa) } From a11a61cf5b0eda14bf8d98b3d6e1f35c4a48b10b Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 12:52:25 +0100 Subject: [PATCH 03/16] Add count to UnorderedFoldable --- .../main/scala/cats/UnorderedFoldable.scala | 22 ++++++++++++++++++- .../scala/cats/syntax/unorderedFoldable.scala | 4 +--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 4336908d1f..0e3929f019 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -1,8 +1,8 @@ package cats +import cats.instances.long._ import cats.kernel.CommutativeMonoid import simulacrum.typeclass -import cats.instances.long._ /** * `UnorderedFoldable` is like a `Foldable` for unordered containers. @@ -48,6 +48,26 @@ import cats.instances.long._ * Note: will not terminate for infinite-sized collections. */ def size[A](fa: F[A]): Long = unorderedFoldMap(fa)(_ => 1L) + + /** + * Count the number of elements in the structure that satisfy the given predicate. + * + * For example: + * {{{ + * scala> import cats.implicits._ + * scala> val map1 = Map[Int, String]() + * scala> val p1: String => Boolean = _.length > 0 + * scala> UnorderedFoldable[Map[Int, *]].count(map1)(p1) + * res0: Long = 0 + * + * scala> val map2 = Map(1 -> "hello", 2 -> "world", 3 -> "!") + * scala> val p2: String => Boolean = _.length > 1 + * scala> UnorderedFoldable[Map[Int, *]].count(map2)(p2) + * res1: Long = 2 + * }}} + */ + def count[A](fa: F[A])(p: A => Boolean): Long = + unorderedFoldMap(fa)(a => if (p(a)) 1L else 0L) } object UnorderedFoldable { diff --git a/core/src/main/scala/cats/syntax/unorderedFoldable.scala b/core/src/main/scala/cats/syntax/unorderedFoldable.scala index 567780c529..99f2ee1a3f 100644 --- a/core/src/main/scala/cats/syntax/unorderedFoldable.scala +++ b/core/src/main/scala/cats/syntax/unorderedFoldable.scala @@ -1,8 +1,6 @@ package cats package syntax -import cats.instances.long._ - trait UnorderedFoldableSyntax extends UnorderedFoldable.ToUnorderedFoldableOps { implicit final def catsSyntaxUnorderedFoldableOps[F[_]: UnorderedFoldable, A](fa: F[A]): UnorderedFoldableOps[F, A] = new UnorderedFoldableOps[F, A](fa) @@ -28,5 +26,5 @@ final class UnorderedFoldableOps[F[_], A](private val fa: F[A]) extends AnyVal { * }}} */ def count(p: A => Boolean)(implicit F: UnorderedFoldable[F]): Long = - F.unorderedFoldMap(fa)(a => if (p(a)) 1L else 0L) + F.count(fa)(p) } From 155a6631f1d6d187df66ee3e632c1412777d72c8 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 07:21:00 -0600 Subject: [PATCH 04/16] Add fromOption and fromValidated to ApplicativeError --- .../main/scala/cats/ApplicativeError.scala | 45 ++++++++++++++++++- .../scala/cats/syntax/applicativeError.scala | 18 +++----- .../main/scala/cats/syntax/validated.scala | 2 +- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index ec4ab1bcda..7acd0a0eb5 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -1,6 +1,7 @@ package cats -import cats.data.EitherT +import cats.data.{EitherT, Validated} +import cats.data.Validated.{Invalid, Valid} import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} @@ -186,6 +187,48 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { case Left(e) => raiseError(e) } + /** + * Convert from scala.Option + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import cats.ApplicativeError + * scala> val F = ApplicativeError[Either[String, *], String] + * + * scala> F.fromOption(Some(1), "Empty") + * res0: scala.Either[String, Int] = Right(1) + * + * scala> F.fromOption(Option.empty[Int], "Empty") + * res1: scala.Either[String, Int] = Left(Empty) + * }}} + */ + def fromOption[A](oa: Option[A], ifEmpty: => E): F[A] = + oa match { + case Some(a) => pure(a) + case None => raiseError(ifEmpty) + } + + /** + * Convert from cats.data.Validated + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import cats.ApplicativeError + * + * scala> ApplicativeError[Option, Unit].fromValidated(1.valid[Unit]) + * res0: scala.Option[Int] = Some(1) + * + * scala> ApplicativeError[Option, Unit].fromValidated(().invalid[Int]) + * res1: scala.Option[Int] = None + * }}} + */ + def fromValidated[A](x: Validated[E, A]): F[A] = + x match { + case Invalid(e) => raiseError(e) + case Valid(a) => pure(a) + } } object ApplicativeError { diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index ed58a5cc2a..1e399062aa 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -1,7 +1,6 @@ package cats package syntax -import cats.data.Validated.{Invalid, Valid} import cats.data.{EitherT, Validated} import scala.reflect.ClassTag @@ -19,14 +18,15 @@ trait ApplicativeErrorSyntax { /** * Extension to ApplicativeError in a binary compat way */ -trait ApplicativeErrorExtension { - implicit final def catsSyntaxApplicativeErrorExtension[F[_], E]( +private[syntax] trait ApplicativeErrorExtension { + @deprecated("Use methods on ApplicativeError", "2.1.0-RC1") + final def catsSyntaxApplicativeErrorExtension[F[_], E]( F: ApplicativeError[F, E] ): ApplicativeErrorExtensionOps[F, E] = new ApplicativeErrorExtensionOps(F) } -final class ApplicativeErrorExtensionOps[F[_], E](F: ApplicativeError[F, E]) { +final private[syntax] class ApplicativeErrorExtensionOps[F[_], E](F: ApplicativeError[F, E]) { /** * Convert from scala.Option @@ -44,8 +44,7 @@ final class ApplicativeErrorExtensionOps[F[_], E](F: ApplicativeError[F, E]) { * res1: scala.Either[String, Int] = Left(Empty) * }}} */ - def fromOption[A](oa: Option[A], ifEmpty: => E): F[A] = - ApplicativeError.liftFromOption(oa, ifEmpty)(F) + private[syntax] def fromOption[A](oa: Option[A], ifEmpty: => E): F[A] = F.fromOption(oa, ifEmpty) /** * Convert from cats.data.Validated @@ -62,12 +61,7 @@ final class ApplicativeErrorExtensionOps[F[_], E](F: ApplicativeError[F, E]) { * res1: scala.Option[Int] = None * }}} */ - def fromValidated[A](x: Validated[E, A]): F[A] = - x match { - case Invalid(e) => F.raiseError(e) - case Valid(a) => F.pure(a) - } - + private[syntax] def fromValidated[A](x: Validated[E, A]): F[A] = F.fromValidated(x) } final class ApplicativeErrorIdOps[E](private val e: E) extends AnyVal { diff --git a/core/src/main/scala/cats/syntax/validated.scala b/core/src/main/scala/cats/syntax/validated.scala index 29f1b4ad55..758818a36f 100644 --- a/core/src/main/scala/cats/syntax/validated.scala +++ b/core/src/main/scala/cats/syntax/validated.scala @@ -21,7 +21,7 @@ trait ValidatedExtensionSyntax { final class ValidatedExtension[E, A](private val self: Validated[E, A]) extends AnyVal { def liftTo[F[_]](implicit F: ApplicativeError[F, _ >: E]): F[A] = - new ApplicativeErrorExtensionOps(F).fromValidated(self) + F.fromValidated(self) } private[syntax] trait ValidatedSyntaxBincompat0 { From 7c4302765b1b454e7fc621484f01d3e4ba561e52 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 07:42:18 -0600 Subject: [PATCH 05/16] Add leftTraverse and leftSequence to Bitraverse --- core/src/main/scala/cats/Bitraverse.scala | 48 ++++++++++++++++++- .../main/scala/cats/syntax/bitraverse.scala | 4 +- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Bitraverse.scala b/core/src/main/scala/cats/Bitraverse.scala index dd521cf236..a0439e4137 100644 --- a/core/src/main/scala/cats/Bitraverse.scala +++ b/core/src/main/scala/cats/Bitraverse.scala @@ -1,6 +1,6 @@ package cats -import simulacrum.typeclass +import simulacrum.{noop, typeclass} /** * A type class abstracting over types that give rise to two independent [[cats.Traverse]]s. @@ -61,6 +61,52 @@ import simulacrum.typeclass override def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D] = bitraverse[Id, A, B, C, D](fab)(f, g) + + /** + * Traverse over the left side of the structure. + * For the right side, use the standard `traverse` from [[cats.Traverse]]. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * + * scala> val intAndString: (Int, String) = (7, "test") + * + * scala> Bitraverse[Tuple2].leftTraverse(intAndString)(i => Option(i).filter(_ > 5)) + * res1: Option[(Int, String)] = Some((7,test)) + * + * scala> Bitraverse[Tuple2].leftTraverse(intAndString)(i => Option(i).filter(_ < 5)) + * res2: Option[(Int, String)] = None + * }}} + */ + @noop + def leftTraverse[G[_], A, B, C](fab: F[A, B])(f: A => G[C])(implicit G: Applicative[G]): G[F[C, B]] = + bitraverse(fab)(f, G.pure(_)) + + /** + * Sequence the left side of the structure. + * For the right side, use the standard `sequence` from [[cats.Traverse]]. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * + * scala> val optionalErrorRight: Either[Option[String], Int] = Either.right(123) + * scala> optionalErrorRight.leftSequence + * res1: Option[Either[String, Int]] = Some(Right(123)) + * + * scala> val optionalErrorLeftSome: Either[Option[String], Int] = Either.left(Some("something went wrong")) + * scala> optionalErrorLeftSome.leftSequence + * res2: Option[Either[String, Int]] = Some(Left(something went wrong)) + * + * scala> val optionalErrorLeftNone: Either[Option[String], Int] = Either.left(None) + * scala> optionalErrorLeftNone.leftSequence + * res3: Option[Either[String,Int]] = None + * }}} + */ + @noop + def leftSequence[G[_], A, B](fgab: F[G[A], B])(implicit G: Applicative[G]): G[F[A, B]] = + bitraverse(fgab)(identity, G.pure(_)) } private[cats] trait ComposedBitraverse[F[_, _], G[_, _]] diff --git a/core/src/main/scala/cats/syntax/bitraverse.scala b/core/src/main/scala/cats/syntax/bitraverse.scala index 4e72c0b4ee..b09661f267 100644 --- a/core/src/main/scala/cats/syntax/bitraverse.scala +++ b/core/src/main/scala/cats/syntax/bitraverse.scala @@ -54,7 +54,7 @@ final private[syntax] class BitraverseOpsBinCompat0[F[_, _], A, B](val fab: F[A, * }}} */ def leftTraverse[G[_], C](f: A => G[C])(implicit F: Bitraverse[F], G: Applicative[G]): G[F[C, B]] = - F.bitraverse(fab)(f, G.pure(_)) + F.leftTraverse[G, A, B, C](fab)(f) } final class LeftNestedBitraverseOps[F[_, _], G[_], A, B](val fgab: F[G[A], B]) extends AnyVal { @@ -81,5 +81,5 @@ final class LeftNestedBitraverseOps[F[_, _], G[_], A, B](val fgab: F[G[A], B]) e * }}} */ def leftSequence(implicit F: Bitraverse[F], G: Applicative[G]): G[F[A, B]] = - F.bitraverse(fgab)(identity, G.pure(_)) + F.leftSequence(fgab) } From d8e00891b7d0348b21b8733b13cfce3efc6cabf7 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 07:46:29 -0600 Subject: [PATCH 06/16] Add @noop to avoid generating redundant syntax methods --- core/src/main/scala/cats/Apply.scala | 4 ++-- core/src/main/scala/cats/TraverseFilter.scala | 3 ++- core/src/main/scala/cats/UnorderedFoldable.scala | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index e1e3265153..fe9308b8c2 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -1,7 +1,6 @@ package cats -import simulacrum.typeclass -import simulacrum.noop +import simulacrum.{noop, typeclass} import cats.data.Ior /** @@ -242,6 +241,7 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit * * }}} */ + @noop def ifA[A](fcond: F[Boolean])(ifTrue: F[A], ifFalse: F[A]): F[A] = { def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse ap2(map(fcond)(ite))(ifTrue, ifFalse) diff --git a/core/src/main/scala/cats/TraverseFilter.scala b/core/src/main/scala/cats/TraverseFilter.scala index b4e19c50f5..0bf3f7330a 100644 --- a/core/src/main/scala/cats/TraverseFilter.scala +++ b/core/src/main/scala/cats/TraverseFilter.scala @@ -1,6 +1,6 @@ package cats -import simulacrum.typeclass +import simulacrum.{noop, typeclass} /** * `TraverseFilter`, also known as `Witherable`, represents list-like structures @@ -42,6 +42,7 @@ trait TraverseFilter[F[_]] extends FunctorFilter[F] { * b: Either[String, List[Int]] = Right(List(1, 5, 3)) * }}} * */ + @noop def sequenceFilter[G[_], A](fgoa: F[G[Option[A]]])(implicit G: Applicative[G]): G[F[A]] = traverseFilter(fgoa)(identity) diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 0e3929f019..683575f3e1 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -2,7 +2,7 @@ package cats import cats.instances.long._ import cats.kernel.CommutativeMonoid -import simulacrum.typeclass +import simulacrum.{noop, typeclass} /** * `UnorderedFoldable` is like a `Foldable` for unordered containers. @@ -66,6 +66,7 @@ import simulacrum.typeclass * res1: Long = 2 * }}} */ + @noop def count[A](fa: F[A])(p: A => Boolean): Long = unorderedFoldMap(fa)(a => if (p(a)) 1L else 0L) } From 731bc13d84321635c8eaa1914421732a1382f46f Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 07:55:04 -0600 Subject: [PATCH 07/16] Add foreverM, iterateForeverM, and untilDefinedM to FlatMap --- core/src/main/scala/cats/FlatMap.scala | 46 +++++++++++++++++++ core/src/main/scala/cats/syntax/flatMap.scala | 22 ++------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index bb939cca0e..c75ee126b5 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -149,4 +149,50 @@ import simulacrum.noop */ def flatTap[A, B](fa: F[A])(f: A => F[B]): F[A] = flatMap(fa)(a => as(f(a), a)) + + /** + * Like an infinite loop of >> calls. This is most useful effect loops + * that you want to run forever in for instance a server. + * + * This will be an infinite loop, or it will return an F[Nothing]. + * + * Be careful using this. + * For instance, a List of length k will produce a list of length k^n at iteration + * n. This means if k = 0, we return an empty list, if k = 1, we loop forever + * allocating single element lists, but if we have a k > 1, we will allocate + * exponentially increasing memory and very quickly OOM. + */ + @noop + def foreverM[A, B](fa: F[A]): F[B] = { + // allocate two things once for efficiency. + val leftUnit = Left(()) + val stepResult: F[Either[Unit, B]] = map(fa)(_ => leftUnit) + tailRecM(())(_ => stepResult) + } + + /** + * iterateForeverM is almost exclusively useful for effect types. For instance, + * A may be some state, we may take the current state, run some effect to get + * a new state and repeat. + */ + @noop + def iterateForeverM[A, B](a: A)(f: A => F[A]): F[B] = + tailRecM[A, B](a)(f.andThen { fa => + map(fa)(Left(_): Either[A, B]) + }) + + /** + * This repeats an F until we get defined values. This can be useful + * for polling type operations on State (or RNG) Monads, or in effect + * monads. + */ + @noop + def untilDefinedM[A](foa: F[Option[A]]): F[A] = { + val leftUnit: Either[Unit, A] = Left(()) + val feither: F[Either[Unit, A]] = map(foa) { + case None => leftUnit + case Some(a) => Right(a) + } + tailRecM(())(_ => feither) + } } diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index 7cf0e24daf..17f8e15e77 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -54,13 +54,7 @@ final class FlatMapOps[F[_], A](private val fa: F[A]) extends AnyVal { * allocating single element lists, but if we have a k > 1, we will allocate * exponentially increasing memory and very quickly OOM. */ - def foreverM[B](implicit F: FlatMap[F]): F[B] = { - // allocate two things once for efficiency. - val leftUnit = Left(()) - val stepResult: F[Either[Unit, B]] = F.map(fa)(_ => leftUnit) - F.tailRecM(())(_ => stepResult) - } - + def foreverM[B](implicit F: FlatMap[F]): F[B] = F.foreverM[A, B](fa) } final class FlattenOps[F[_], A](private val ffa: F[F[A]]) extends AnyVal { @@ -124,10 +118,7 @@ final class FlatMapIdOps[A](private val a: A) extends AnyVal { * A may be some state, we may take the current state, run some effect to get * a new state and repeat. */ - def iterateForeverM[F[_], B](f: A => F[A])(implicit F: FlatMap[F]): F[B] = - tailRecM[F, B](f.andThen { fa => - F.map(fa)(Left(_): Either[A, B]) - }) + def iterateForeverM[F[_], B](f: A => F[A])(implicit F: FlatMap[F]): F[B] = F.iterateForeverM[A, B](a)(f) } trait FlatMapOptionSyntax { @@ -142,12 +133,5 @@ final class FlatMapOptionOps[F[_], A](private val fopta: F[Option[A]]) extends A * for polling type operations on State (or RNG) Monads, or in effect * monads. */ - def untilDefinedM(implicit F: FlatMap[F]): F[A] = { - val leftUnit: Either[Unit, A] = Left(()) - val feither: F[Either[Unit, A]] = F.map(fopta) { - case None => leftUnit - case Some(a) => Right(a) - } - F.tailRecM(())(_ => feither) - } + def untilDefinedM(implicit F: FlatMap[F]): F[A] = F.untilDefinedM[A](fopta) } From 63b2d8bd2888efa0bbfb1bbf0844088d4f31f338 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:01:03 -0600 Subject: [PATCH 08/16] Add findM to Foldable --- core/src/main/scala/cats/Foldable.scala | 32 ++++++++++++++++++- .../src/main/scala/cats/syntax/foldable.scala | 6 +--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 5b672a7d3d..aa797dbe5f 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -3,7 +3,7 @@ package cats import scala.collection.mutable import cats.instances.either._ import cats.kernel.CommutativeMonoid -import simulacrum.typeclass +import simulacrum.{noop, typeclass} import Foldable.sentinel /** @@ -423,6 +423,36 @@ import Foldable.sentinel if (f(a)) Now(Some(a)) else lb }.value + /** + * Find the first element matching the effectful predicate, if one exists. + * + * If there are no elements, the result is `None`. `findM` short-circuits, + * i.e. once an element is found, no further effects are produced. + * + * For example: + * {{{ + * scala> import cats.implicits._ + * scala> val list = List(1,2,3,4) + * scala> Foldable[List].findM(list)(n => (n >= 2).asRight[String]) + * res0: Either[String,Option[Int]] = Right(Some(2)) + * + * scala> Foldable[List].findM(list)(n => (n > 4).asRight[String]) + * res1: Either[String,Option[Int]] = Right(None) + * + * scala> Foldable[List].findM(list)(n => Either.cond(n < 3, n >= 2, "error")) + * res2: Either[String,Option[Int]] = Right(Some(2)) + * + * scala> Foldable[List].findM(list)(n => Either.cond(n < 3, false, "error")) + * res3: Either[String,Option[Int]] = Left(error) + * }}} + */ + @noop + def findM[G[_], A](fa: F[A])(p: A => G[Boolean])(implicit G: Monad[G]): G[Option[A]] = + G.tailRecM(Foldable.Source.fromFoldable(fa)(self))(_.uncons match { + case Some((a, src)) => G.map(p(a))(if (_) Right(Some(a)) else Left(src.value)) + case None => G.pure(Right(None)) + }) + /** * Check whether at least one element satisfies the predicate. * diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 53157f0c99..b2cde5a8a7 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -166,11 +166,7 @@ final class FoldableOps[F[_], A](private val fa: F[A]) extends AnyVal { * res3: Either[String,Option[Int]] = Left(error) * }}} */ - def findM[G[_]](p: A => G[Boolean])(implicit F: Foldable[F], G: Monad[G]): G[Option[A]] = - G.tailRecM(Foldable.Source.fromFoldable(fa))(_.uncons match { - case Some((a, src)) => G.map(p(a))(if (_) Right(Some(a)) else Left(src.value)) - case None => G.pure(Right(None)) - }) + def findM[G[_]](p: A => G[Boolean])(implicit F: Foldable[F], G: Monad[G]): G[Option[A]] = F.findM[G, A](fa)(p) /** * Tear down a subset of this structure using a `PartialFunction`. From 0ea41834c68b24a41f5fc61f3e467c09fab12d35 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:07:19 -0600 Subject: [PATCH 09/16] Add reduceMapK to Reducible --- core/src/main/scala/cats/Reducible.scala | 18 +++++++++++++++++- .../src/main/scala/cats/syntax/reducible.scala | 3 +-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index f41161b558..6bb748a413 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -1,7 +1,7 @@ package cats import cats.data.{Ior, NonEmptyList} -import simulacrum.typeclass +import simulacrum.{noop, typeclass} /** * Data structures that can be reduced to a summary value. @@ -54,6 +54,22 @@ import simulacrum.typeclass def reduceMap[A, B](fa: F[A])(f: A => B)(implicit B: Semigroup[B]): B = reduceLeftTo(fa)(f)((b, a) => B.combine(b, f(a))) + /** + * Apply `f` to each element of `fa` and combine them using the + * given `SemigroupK[G]`. + * + * {{{ + * scala> import cats._, cats.data._, cats.implicits._ + * scala> val f: Int => Endo[String] = i => (s => s + i) + * scala> val x: Endo[String] = Reducible[NonEmptyList].reduceMapK(NonEmptyList.of(1, 2, 3))(f) + * scala> val a = x("foo") + * a: String = "foo321" + * }}} + * */ + @noop + def reduceMapK[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: SemigroupK[G]): G[B] = + reduceLeftTo(fa)(f)((b, a) => G.combineK(b, f(a))) + /** * Apply `f` to the "initial element" of `fa` and combine it with * every other value using the given function `g`. diff --git a/core/src/main/scala/cats/syntax/reducible.scala b/core/src/main/scala/cats/syntax/reducible.scala index 3e5904ef52..76e1ff1faf 100644 --- a/core/src/main/scala/cats/syntax/reducible.scala +++ b/core/src/main/scala/cats/syntax/reducible.scala @@ -29,6 +29,5 @@ final class ReducibleOps0[F[_], A](private val fa: F[A]) extends AnyVal { * a: String = "foo321" * }}} * */ - def reduceMapK[G[_], B](f: A => G[B])(implicit F: Reducible[F], G: SemigroupK[G]): G[B] = - F.reduceLeftTo(fa)(f)((b, a) => G.combineK(b, f(a))) + def reduceMapK[G[_], B](f: A => G[B])(implicit F: Reducible[F], G: SemigroupK[G]): G[B] = F.reduceMapK[G, A, B](fa)(f) } From 762cb2c0771a7ae8f73babc088cb3e48f6db216a Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:12:29 -0600 Subject: [PATCH 10/16] Add collectFirstSomeM to Foldable --- core/src/main/scala/cats/Foldable.scala | 39 +++++++++++++++++++ .../src/main/scala/cats/syntax/foldable.scala | 9 +---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index aa797dbe5f..13878c882a 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -269,6 +269,45 @@ import Foldable.sentinel if (ob.isDefined) Eval.now(ob) else lb }.value + /** + * Monadic version of `collectFirstSome`. + * + * If there are no elements, the result is `None`. `collectFirstSomeM` short-circuits, + * i.e. once a Some element is found, no further effects are produced. + * + * For example: + * {{{ + * scala> import cats.implicits._ + * scala> def parseInt(s: String): Either[String, Int] = Either.catchOnly[NumberFormatException](s.toInt).leftMap(_.getMessage) + * scala> val keys1 = List("1", "2", "4", "5") + * scala> val map1 = Map(4 -> "Four", 5 -> "Five") + * scala> Foldable[List].collectFirstSomeM(keys1)(parseInt(_) map map1.get) + * res0: scala.util.Either[String,Option[String]] = Right(Some(Four)) + * + * scala> val map2 = Map(6 -> "Six", 7 -> "Seven") + * scala> Foldable[List].collectFirstSomeM(keys1)(parseInt(_) map map2.get) + * res1: scala.util.Either[String,Option[String]] = Right(None) + * + * scala> val keys2 = List("1", "x", "4", "5") + * scala> Foldable[List].collectFirstSomeM(keys2)(parseInt(_) map map1.get) + * res2: scala.util.Either[String,Option[String]] = Left(For input string: "x") + * + * scala> val keys3 = List("1", "2", "4", "x") + * scala> Foldable[List].collectFirstSomeM(keys3)(parseInt(_) map map1.get) + * res3: scala.util.Either[String,Option[String]] = Right(Some(Four)) + * }}} + */ + @noop + def collectFirstSomeM[G[_], A, B](fa: F[A])(f: A => G[Option[B]])(implicit G: Monad[G]): G[Option[B]] = + G.tailRecM(Foldable.Source.fromFoldable(fa)(self))(_.uncons match { + case Some((a, src)) => + G.map(f(a)) { + case None => Left(src.value) + case s => Right(s) + } + case None => G.pure(Right(None)) + }) + /** * Fold implemented using the given Monoid[A] instance. */ diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index b2cde5a8a7..fe1a2866ee 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -134,14 +134,7 @@ final class FoldableOps[F[_], A](private val fa: F[A]) extends AnyVal { * }}} */ def collectFirstSomeM[G[_], B](f: A => G[Option[B]])(implicit F: Foldable[F], G: Monad[G]): G[Option[B]] = - G.tailRecM(Foldable.Source.fromFoldable(fa))(_.uncons match { - case Some((a, src)) => - G.map(f(a)) { - case None => Left(src.value) - case s => Right(s) - } - case None => G.pure(Right(None)) - }) + F.collectFirstSomeM[G, A, B](fa)(f) /** * Find the first element matching the effectful predicate, if one exists. From 30d556e4654e45fc781ccf389d75953a5b9d8e23 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:20:00 -0600 Subject: [PATCH 11/16] Add collectFold and collectFoldSome to Foldable --- core/src/main/scala/cats/Foldable.scala | 32 +++++++++++++++++++ .../src/main/scala/cats/syntax/foldable.scala | 13 ++------ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 13878c882a..3a24d9134e 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -308,6 +308,38 @@ import Foldable.sentinel case None => G.pure(Right(None)) }) + /** + * Tear down a subset of this structure using a `PartialFunction`. + *{{{ + * scala> import cats.implicits._ + * scala> val xs = List(1, 2, 3, 4) + * scala> Foldable[List].collectFold(xs) { case n if n % 2 == 0 => n } + * res0: Int = 6 + *}}} + */ + @noop + def collectFold[A, B](fa: F[A])(f: PartialFunction[A, B])(implicit B: Monoid[B]): B = + foldLeft(fa, B.empty)((acc, a) => B.combine(acc, f.applyOrElse(a, (_: A) => B.empty))) + + /** + * Tear down a subset of this structure using a `A => Option[M]`. + *{{{ + * scala> import cats.implicits._ + * scala> val xs = List(1, 2, 3, 4) + * scala> def f(n: Int): Option[Int] = if (n % 2 == 0) Some(n) else None + * scala> Foldable[List].collectSomeFold(xs)(f) + * res0: Int = 6 + *}}} + */ + def collectFoldSome[A, B](fa: F[A])(f: A => Option[B])(implicit B: Monoid[B]): B = + foldLeft(fa, B.empty)( + (acc, a) => + f(a) match { + case Some(x) => B.combine(acc, x) + case None => acc + } + ) + /** * Fold implemented using the given Monoid[A] instance. */ diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index fe1a2866ee..843f12be35 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -170,8 +170,7 @@ final class FoldableOps[F[_], A](private val fa: F[A]) extends AnyVal { * res0: Int = 6 *}}} */ - def collectFold[M](f: PartialFunction[A, M])(implicit F: Foldable[F], M: Monoid[M]): M = - F.foldLeft(fa, M.empty)((acc, a) => M.combine(acc, f.applyOrElse(a, (_: A) => M.empty))) + def collectFold[M](f: PartialFunction[A, M])(implicit F: Foldable[F], M: Monoid[M]): M = F.collectFold[A, M](fa)(f) /** * Tear down a subset of this structure using a `A => Option[M]`. @@ -183,14 +182,8 @@ final class FoldableOps[F[_], A](private val fa: F[A]) extends AnyVal { * res0: Int = 6 *}}} */ - def collectSomeFold[M](f: A => Option[M])(implicit F: Foldable[F], M: Monoid[M]): M = - F.foldLeft(fa, M.empty)( - (acc, a) => - f(a) match { - case Some(x) => M.combine(acc, x) - case None => acc - } - ) + @deprecated("Use collectFoldSome", "2.1.0-RC1") + def collectSomeFold[M](f: A => Option[M])(implicit F: Foldable[F], M: Monoid[M]): M = F.collectFoldSome[A, M](fa)(f) } final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { From 20deedb3d040a387fa8271bc609cb81ebb656ded Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:27:26 -0600 Subject: [PATCH 12/16] Fix doc test --- core/src/main/scala/cats/Foldable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 3a24d9134e..a895fb5686 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -327,7 +327,7 @@ import Foldable.sentinel * scala> import cats.implicits._ * scala> val xs = List(1, 2, 3, 4) * scala> def f(n: Int): Option[Int] = if (n % 2 == 0) Some(n) else None - * scala> Foldable[List].collectSomeFold(xs)(f) + * scala> Foldable[List].collectFoldSome(xs)(f) * res0: Int = 6 *}}} */ From b689e54ceedcd3ac9ee1661e57815cc04904f063 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:34:45 -0600 Subject: [PATCH 13/16] Fix doc test --- core/src/main/scala/cats/syntax/foldable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 843f12be35..9af56d091d 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -178,7 +178,7 @@ final class FoldableOps[F[_], A](private val fa: F[A]) extends AnyVal { * scala> import cats.implicits._ * scala> val xs = List(1, 2, 3, 4) * scala> def f(n: Int): Option[Int] = if (n % 2 == 0) Some(n) else None - * scala> xs.collectSomeFold(f) + * scala> xs.collectFoldSome(f) * res0: Int = 6 *}}} */ From 5867e6673a0f98c56d962df753577f964641b7d1 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:27:02 -0600 Subject: [PATCH 14/16] Move Foldable extension methods onto Foldable --- core/src/main/scala/cats/Foldable.scala | 78 +++++++++++++++++++ .../src/main/scala/cats/syntax/foldable.scala | 52 ++++--------- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index a895fb5686..aae1941fdb 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -726,6 +726,84 @@ import Foldable.sentinel override def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: (A) => B): B = foldMap(fa)(f) + + /** + * Separate this Foldable into a Tuple by a separating function `A => H[B, C]` for some `Bifoldable[H]` + * Equivalent to `Functor#map` and then `Alternative#separate`. + * + * {{{ + * scala> import cats.implicits._, cats.Foldable, cats.data.Const + * scala> val list = List(1,2,3,4) + * scala> Foldable[List].partitionBifold(list)(a => ("value " + a.toString(), if (a % 2 == 0) -a else a)) + * res0: (List[String], List[Int]) = (List(value 1, value 2, value 3, value 4),List(1, -2, 3, -4)) + * scala> Foldable[List].partitionBifold(list)(a => Const[Int, Nothing with Any](a)) + * res1: (List[Int], List[Nothing with Any]) = (List(1, 2, 3, 4),List()) + * }}} + */ + @noop + def partitionBifold[H[_, _], A, B, C](fa: F[A])(f: A => H[B, C])(implicit A: Alternative[F], + H: Bifoldable[H]): (F[B], F[C]) = { + import cats.instances.tuple._ + + implicit val mb: Monoid[F[B]] = A.algebra[B] + implicit val mc: Monoid[F[C]] = A.algebra[C] + + foldMap[A, (F[B], F[C])](fa)( + a => H.bifoldMap[B, C, (F[B], F[C])](f(a))(b => (A.pure(b), A.empty[C]), c => (A.empty[B], A.pure(c))) + ) + } + + /** + * Separate this Foldable into a Tuple by an effectful separating function `A => G[H[B, C]]` for some `Bifoldable[H]` + * Equivalent to `Traverse#traverse` over `Alternative#separate` + * + * {{{ + * scala> import cats.implicits._, cats.Foldable, cats.data.Const + * scala> val list = List(1,2,3,4) + * `Const`'s second parameter is never instantiated, so we can use an impossible type: + * scala> Foldable[List].partitionBifoldM(list)(a => Option(Const[Int, Nothing with Any](a))) + * res0: Option[(List[Int], List[Nothing with Any])] = Some((List(1, 2, 3, 4),List())) + * }}} + */ + @noop + def partitionBifoldM[G[_], H[_, _], A, B, C]( + fa: F[A] + )(f: A => G[H[B, C]])(implicit A: Alternative[F], M: Monad[G], H: Bifoldable[H]): G[(F[B], F[C])] = { + import cats.instances.tuple._ + + implicit val mb: Monoid[F[B]] = A.algebra[B] + implicit val mc: Monoid[F[C]] = A.algebra[C] + + foldMapM[G, A, (F[B], F[C])](fa)( + a => + M.map(f(a)) { + H.bifoldMap[B, C, (F[B], F[C])](_)(b => (A.pure(b), A.empty[C]), c => (A.empty[B], A.pure(c))) + } + ) + } + + /** + * Separate this Foldable into a Tuple by an effectful separating function `A => G[Either[B, C]]` + * Equivalent to `Traverse#traverse` over `Alternative#separate` + * + * {{{ + * scala> import cats.implicits._, cats.Foldable, cats.Eval + * scala> val list = List(1,2,3,4) + * scala> val partitioned1 = Foldable[List].partitionEitherM(list)(a => if (a % 2 == 0) Eval.now(Either.left[String, Int](a.toString)) else Eval.now(Either.right[String, Int](a))) + * Since `Eval.now` yields a lazy computation, we need to force it to inspect the result: + * scala> partitioned1.value + * res0: (List[String], List[Int]) = (List(2, 4),List(1, 3)) + * scala> val partitioned2 = Foldable[List].partitionEitherM(list)(a => Eval.later(Either.right(a * 4))) + * scala> partitioned2.value + * res1: (List[Nothing], List[Int]) = (List(),List(4, 8, 12, 16)) + * }}} + */ + @noop + def partitionEitherM[G[_], A, B, C](fa: F[A])(f: A => G[Either[B, C]])(implicit A: Alternative[F], + M: Monad[G]): G[(F[B], F[C])] = { + import cats.instances.either._ + partitionBifoldM[G, Either, A, B, C](fa)(f)(A, M, Bifoldable[Either]) + } } object Foldable { diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 9af56d091d..c4af904bcb 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -15,7 +15,8 @@ private[syntax] trait FoldableSyntaxBinCompat0 { } private[syntax] trait FoldableSyntaxBinCompat1 { - implicit final def catsSyntaxFoldableBinCompat0[F[_]](fa: Foldable[F]): FoldableOps1[F] = + @deprecated("Use methods on Foldable", "2.1.0-RC1") + final def catsSyntaxFoldableBinCompat0[F[_]](fa: Foldable[F]): FoldableOps1[F] = new FoldableOps1(fa) } @@ -239,10 +240,8 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { */ def partitionBifold[H[_, _], B, C]( f: A => H[B, C] - )(implicit A: Alternative[F], F: Foldable[F], H: Bifoldable[H]): (F[B], F[C]) = { - import cats.syntax.foldable._ + )(implicit A: Alternative[F], F: Foldable[F], H: Bifoldable[H]): (F[B], F[C]) = F.partitionBifold[H, A, B, C](fa)(f)(A, H) - } /** * Separate this Foldable into a Tuple by an effectful separating function `A => G[H[B, C]]` for some `Bifoldable[H]` @@ -258,10 +257,8 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { */ def partitionBifoldM[G[_], H[_, _], B, C]( f: A => G[H[B, C]] - )(implicit A: Alternative[F], F: Foldable[F], M: Monad[G], H: Bifoldable[H]): G[(F[B], F[C])] = { - import cats.syntax.foldable._ + )(implicit A: Alternative[F], F: Foldable[F], M: Monad[G], H: Bifoldable[H]): G[(F[B], F[C])] = F.partitionBifoldM[G, H, A, B, C](fa)(f)(A, M, H) - } /** * Separate this Foldable into a Tuple by an effectful separating function `A => G[Either[B, C]]` @@ -281,12 +278,11 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { */ def partitionEitherM[G[_], B, C]( f: A => G[Either[B, C]] - )(implicit A: Alternative[F], F: Foldable[F], M: Monad[G]): G[(F[B], F[C])] = { - import cats.syntax.foldable._ + )(implicit A: Alternative[F], F: Foldable[F], M: Monad[G]): G[(F[B], F[C])] = F.partitionEitherM[G, A, B, C](fa)(f)(A, M) - } } +@deprecated("Use methods on Foldable", "2.1.0-RC1") final private[syntax] class FoldableOps1[F[_]](private val F: Foldable[F]) extends AnyVal { /** @@ -302,17 +298,10 @@ final private[syntax] class FoldableOps1[F[_]](private val F: Foldable[F]) exten * res1: (List[Int], List[Nothing with Any]) = (List(1, 2, 3, 4),List()) * }}} */ + @deprecated("Use partitionBifold on Foldable", "2.1.0-RC1") def partitionBifold[H[_, _], A, B, C](fa: F[A])(f: A => H[B, C])(implicit A: Alternative[F], - H: Bifoldable[H]): (F[B], F[C]) = { - import cats.instances.tuple._ - - implicit val mb: Monoid[F[B]] = A.algebra[B] - implicit val mc: Monoid[F[C]] = A.algebra[C] - - F.foldMap[A, (F[B], F[C])](fa)( - a => H.bifoldMap[B, C, (F[B], F[C])](f(a))(b => (A.pure(b), A.empty[C]), c => (A.empty[B], A.pure(c))) - ) - } + H: Bifoldable[H]): (F[B], F[C]) = + F.partitionBifold[H, A, B, C](fa)(f) /** * Separate this Foldable into a Tuple by an effectful separating function `A => G[H[B, C]]` for some `Bifoldable[H]` @@ -326,21 +315,11 @@ final private[syntax] class FoldableOps1[F[_]](private val F: Foldable[F]) exten * res0: Option[(List[Int], List[Nothing with Any])] = Some((List(1, 2, 3, 4),List())) * }}} */ + @deprecated("Use partitionBifoldM on Foldable", "2.1.0-RC1") def partitionBifoldM[G[_], H[_, _], A, B, C]( fa: F[A] - )(f: A => G[H[B, C]])(implicit A: Alternative[F], M: Monad[G], H: Bifoldable[H]): G[(F[B], F[C])] = { - import cats.instances.tuple._ - - implicit val mb: Monoid[F[B]] = A.algebra[B] - implicit val mc: Monoid[F[C]] = A.algebra[C] - - F.foldMapM[G, A, (F[B], F[C])](fa)( - a => - M.map(f(a)) { - H.bifoldMap[B, C, (F[B], F[C])](_)(b => (A.pure(b), A.empty[C]), c => (A.empty[B], A.pure(c))) - } - ) - } + )(f: A => G[H[B, C]])(implicit A: Alternative[F], M: Monad[G], H: Bifoldable[H]): G[(F[B], F[C])] = + F.partitionBifoldM[G, H, A, B, C](fa)(f) /** * Separate this Foldable into a Tuple by an effectful separating function `A => G[Either[B, C]]` @@ -358,9 +337,8 @@ final private[syntax] class FoldableOps1[F[_]](private val F: Foldable[F]) exten * res1: (List[Nothing], List[Int]) = (List(),List(4, 8, 12, 16)) * }}} */ + @deprecated("Use partitionEitherM on Foldable", "2.1.0-RC1") def partitionEitherM[G[_], A, B, C](fa: F[A])(f: A => G[Either[B, C]])(implicit A: Alternative[F], - M: Monad[G]): G[(F[B], F[C])] = { - import cats.instances.either._ - partitionBifoldM[G, Either, A, B, C](fa)(f)(A, M, Bifoldable[Either]) - } + M: Monad[G]): G[(F[B], F[C])] = + F.partitionEitherM[G, A, B, C](fa)(f) } From 4402e0dfc964c90e891b26613bb1b90fcf6b6664 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:34:18 -0600 Subject: [PATCH 15/16] Add foldMapK to Foldable --- core/src/main/scala/cats/Foldable.scala | 16 ++++++++++++++++ core/src/main/scala/cats/syntax/foldable.scala | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index aae1941fdb..681fa84d55 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -384,6 +384,22 @@ import Foldable.sentinel } } + /** + * Fold implemented by mapping `A` values into `B` in a context `G` and then + * combining them using the `MonoidK[G]` instance. + * + * {{{ + * scala> import cats._, cats.implicits._ + * scala> val f: Int => Endo[String] = i => (s => s + i) + * scala> val x: Endo[String] = Foldable[List].foldMapK(List(1, 2, 3))(f) + * scala> val a = x("foo") + * a: String = "foo321" + * }}} + * */ + @noop + def foldMapK[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: MonoidK[G]): G[B] = + foldMap(fa)(f)(G.algebra) + /** * Alias for [[foldM]]. */ diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index c4af904bcb..ebba15ea7f 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -221,8 +221,7 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { * a: String = "foo321" * }}} * */ - def foldMapK[G[_], B](f: A => G[B])(implicit F: Foldable[F], G: MonoidK[G]): G[B] = - F.foldMap(fa)(f)(G.algebra) + def foldMapK[G[_], B](f: A => G[B])(implicit F: Foldable[F], G: MonoidK[G]): G[B] = F.foldMapK(fa)(f) /** * Separate this Foldable into a Tuple by an effectful separating function `A => H[B, C]` for some `Bifoldable[H]` From 7b30d5b2463f9e85d0db6054cf9d9f290b2a774c Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Thu, 14 Nov 2019 08:50:03 -0600 Subject: [PATCH 16/16] Clean up redundant syntax methods --- .../scala/cats/syntax/applicativeError.scala | 34 ++------------- .../src/main/scala/cats/syntax/foldable.scala | 41 ------------------- 2 files changed, 3 insertions(+), 72 deletions(-) diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 1e399062aa..10125e46fe 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -26,41 +26,13 @@ private[syntax] trait ApplicativeErrorExtension { new ApplicativeErrorExtensionOps(F) } +@deprecated("Use methods on ApplicativeError", "2.1.0-RC1") final private[syntax] class ApplicativeErrorExtensionOps[F[_], E](F: ApplicativeError[F, E]) { - /** - * Convert from scala.Option - * - * Example: - * {{{ - * scala> import cats.implicits._ - * scala> import cats.ApplicativeError - * scala> val F = ApplicativeError[Either[String, *], String] - * - * scala> F.fromOption(Some(1), "Empty") - * res0: scala.Either[String, Int] = Right(1) - * - * scala> F.fromOption(Option.empty[Int], "Empty") - * res1: scala.Either[String, Int] = Left(Empty) - * }}} - */ + @deprecated("Use fromOption on ApplicativeError", "2.1.0-RC1") private[syntax] def fromOption[A](oa: Option[A], ifEmpty: => E): F[A] = F.fromOption(oa, ifEmpty) - /** - * Convert from cats.data.Validated - * - * Example: - * {{{ - * scala> import cats.implicits._ - * scala> import cats.ApplicativeError - * - * scala> ApplicativeError[Option, Unit].fromValidated(1.valid[Unit]) - * res0: scala.Option[Int] = Some(1) - * - * scala> ApplicativeError[Option, Unit].fromValidated(().invalid[Int]) - * res1: scala.Option[Int] = None - * }}} - */ + @deprecated("Use fromValidated on ApplicativeError", "2.1.0-RC1") private[syntax] def fromValidated[A](x: Validated[E, A]): F[A] = F.fromValidated(x) } diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index ebba15ea7f..a09b08754a 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -284,58 +284,17 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { @deprecated("Use methods on Foldable", "2.1.0-RC1") final private[syntax] class FoldableOps1[F[_]](private val F: Foldable[F]) extends AnyVal { - /** - * Separate this Foldable into a Tuple by a separating function `A => H[B, C]` for some `Bifoldable[H]` - * Equivalent to `Functor#map` and then `Alternative#separate`. - * - * {{{ - * scala> import cats.implicits._, cats.Foldable, cats.data.Const - * scala> val list = List(1,2,3,4) - * scala> Foldable[List].partitionBifold(list)(a => ("value " + a.toString(), if (a % 2 == 0) -a else a)) - * res0: (List[String], List[Int]) = (List(value 1, value 2, value 3, value 4),List(1, -2, 3, -4)) - * scala> Foldable[List].partitionBifold(list)(a => Const[Int, Nothing with Any](a)) - * res1: (List[Int], List[Nothing with Any]) = (List(1, 2, 3, 4),List()) - * }}} - */ @deprecated("Use partitionBifold on Foldable", "2.1.0-RC1") def partitionBifold[H[_, _], A, B, C](fa: F[A])(f: A => H[B, C])(implicit A: Alternative[F], H: Bifoldable[H]): (F[B], F[C]) = F.partitionBifold[H, A, B, C](fa)(f) - /** - * Separate this Foldable into a Tuple by an effectful separating function `A => G[H[B, C]]` for some `Bifoldable[H]` - * Equivalent to `Traverse#traverse` over `Alternative#separate` - * - * {{{ - * scala> import cats.implicits._, cats.Foldable, cats.data.Const - * scala> val list = List(1,2,3,4) - * `Const`'s second parameter is never instantiated, so we can use an impossible type: - * scala> Foldable[List].partitionBifoldM(list)(a => Option(Const[Int, Nothing with Any](a))) - * res0: Option[(List[Int], List[Nothing with Any])] = Some((List(1, 2, 3, 4),List())) - * }}} - */ @deprecated("Use partitionBifoldM on Foldable", "2.1.0-RC1") def partitionBifoldM[G[_], H[_, _], A, B, C]( fa: F[A] )(f: A => G[H[B, C]])(implicit A: Alternative[F], M: Monad[G], H: Bifoldable[H]): G[(F[B], F[C])] = F.partitionBifoldM[G, H, A, B, C](fa)(f) - /** - * Separate this Foldable into a Tuple by an effectful separating function `A => G[Either[B, C]]` - * Equivalent to `Traverse#traverse` over `Alternative#separate` - * - * {{{ - * scala> import cats.implicits._, cats.Foldable, cats.Eval - * scala> val list = List(1,2,3,4) - * scala> val partitioned1 = Foldable[List].partitionEitherM(list)(a => if (a % 2 == 0) Eval.now(Either.left[String, Int](a.toString)) else Eval.now(Either.right[String, Int](a))) - * Since `Eval.now` yields a lazy computation, we need to force it to inspect the result: - * scala> partitioned1.value - * res0: (List[String], List[Int]) = (List(2, 4),List(1, 3)) - * scala> val partitioned2 = Foldable[List].partitionEitherM(list)(a => Eval.later(Either.right(a * 4))) - * scala> partitioned2.value - * res1: (List[Nothing], List[Int]) = (List(),List(4, 8, 12, 16)) - * }}} - */ @deprecated("Use partitionEitherM on Foldable", "2.1.0-RC1") def partitionEitherM[G[_], A, B, C](fa: F[A])(f: A => G[Either[B, C]])(implicit A: Alternative[F], M: Monad[G]): G[(F[B], F[C])] =