From 02272d469ba6382c9a0e877ea79400d28eff3761 Mon Sep 17 00:00:00 2001 From: Jorge Adriano Branco Aires Date: Thu, 20 Feb 2020 16:05:57 +0000 Subject: [PATCH 1/3] unorderedReduceOption for UnorderedFoldable --- core/src/main/scala/cats/UnorderedFoldable.scala | 6 +++++- laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala | 3 +++ .../scala/cats/laws/discipline/UnorderedFoldableTests.scala | 6 +++++- .../scala/cats/laws/discipline/UnorderedTraverseTests.scala | 3 ++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 683575f3e1..cbbd8611b1 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -1,7 +1,8 @@ package cats import cats.instances.long._ -import cats.kernel.CommutativeMonoid +import cats.kernel.{CommutativeMonoid, CommutativeSemigroup} +import cats.syntax.option._ import simulacrum.{noop, typeclass} /** @@ -14,6 +15,9 @@ import simulacrum.{noop, typeclass} def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = unorderedFoldMap(fa)(identity) + def unorderedReduceOption[A](fa: F[A])(implicit A: CommutativeSemigroup[A]): Option[A] = + unorderedFoldMap(fa)(_.some)(cats.instances.option.catsKernelStdCommutativeMonoidForOption(A)) + /** * Returns true if there are no elements. Otherwise false. */ diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala index 45b765c7ee..1f2256ccc5 100644 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -9,6 +9,9 @@ trait UnorderedFoldableLaws[F[_]] { def unorderedFoldConsistentWithUnorderedFoldMap[A: CommutativeMonoid](fa: F[A]): IsEq[A] = F.unorderedFoldMap(fa)(identity) <-> F.unorderedFold(fa) + def unorderedReduceOptionConsistentWithUnorderedFold[A: CommutativeMonoid](fa: F[A]): IsEq[Option[A]] = + F.unorderedReduceOption(fa) <-> (if (F.isEmpty(fa)) None else Some(F.unorderedFold(fa))) + def forallConsistentWithExists[A](fa: F[A], p: A => Boolean): Boolean = if (F.forall(fa)(p)) { val negationExists = F.exists(fa)(a => !(p(a))) diff --git a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala index 2ec35220de..44c10b14b2 100644 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -18,11 +18,15 @@ trait UnorderedFoldableTests[F[_]] extends Laws { A: CommutativeMonoid[A], B: CommutativeMonoid[B], EqFA: Eq[A], - EqFB: Eq[B]): RuleSet = + EqFB: Eq[B], + EqOptionA: Eq[Option[A]]): RuleSet = new DefaultRuleSet( name = "unorderedFoldable", parent = None, "unorderedFold consistent with unorderedFoldMap" -> forAll(laws.unorderedFoldConsistentWithUnorderedFoldMap[A] _), + "unorderedReduceOption consistent with unorderedFold" -> forAll( + laws.unorderedReduceOptionConsistentWithUnorderedFold[A] _ + ), "forall consistent with exists" -> forAll(laws.forallConsistentWithExists[A] _), "forall true if empty" -> forAll(laws.forallEmpty[A] _), "nonEmpty reference" -> forAll(laws.nonEmptyRef[A] _), diff --git a/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala index 9edc6bdb33..a27dd4820b 100644 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala @@ -23,7 +23,8 @@ trait UnorderedTraverseTests[F[_]] extends UnorderedFoldableTests[F] { EqB: Eq[B], EqXYFC: Eq[X[Y[F[C]]]], EqXFB: Eq[X[F[B]]], - EqYFB: Eq[Y[F[B]]] + EqYFB: Eq[Y[F[B]]], + EqOptionA: Eq[Option[A]] ): RuleSet = { implicit def EqXFBYFB: Eq[(X[F[B]], Y[F[B]])] = new Eq[(X[F[B]], Y[F[B]])] { override def eqv(x: (X[F[B]], Y[F[B]]), y: (X[F[B]], Y[F[B]])): Boolean = From 8ad05410eb1a776037e6da7d0c1298cdeae750b1 Mon Sep 17 00:00:00 2001 From: Jorge Adriano Branco Aires Date: Fri, 21 Feb 2020 10:41:37 +0000 Subject: [PATCH 2/3] introduce min/max option operations --- core/src/main/scala/cats/Foldable.scala | 8 +-- .../main/scala/cats/UnorderedFoldable.scala | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 55e8f5ca58..aac72b321d 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -183,7 +183,7 @@ import Foldable.sentinel * * @see [[maximumOption]] for maximum instead of minimum. */ - def minimumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] = + override def minimumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] = reduceLeftOption(fa)(A.min) /** @@ -197,7 +197,7 @@ import Foldable.sentinel * * @see [[minimumOption]] for minimum instead of maximum. */ - def maximumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] = + override def maximumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] = reduceLeftOption(fa)(A.max) /** @@ -211,7 +211,7 @@ import Foldable.sentinel * * @see [[maximumByOption]] for maximum instead of minimum. */ - def minimumByOption[A, B: Order](fa: F[A])(f: A => B): Option[A] = + override def minimumByOption[A, B: Order](fa: F[A])(f: A => B): Option[A] = minimumOption(fa)(Order.by(f)) /** @@ -225,7 +225,7 @@ import Foldable.sentinel * * @see [[minimumByOption]] for minimum instead of maximum. */ - def maximumByOption[A, B: Order](fa: F[A])(f: A => B): Option[A] = + override def maximumByOption[A, B: Order](fa: F[A])(f: A => B): Option[A] = maximumOption(fa)(Order.by(f)) /** diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index cbbd8611b1..0ea47c91f1 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -73,6 +73,50 @@ import simulacrum.{noop, typeclass} @noop def count[A](fa: F[A])(p: A => Boolean): Long = unorderedFoldMap(fa)(a => if (p(a)) 1L else 0L) + + /** + * Find the minimum `A` item in this structure according to the `Order[A]`. + * + * @return `None` if the structure is empty, otherwise the minimum element + * wrapped in a `Some`. + * + * @see [[maximumOption]] for maximum instead of minimum. + */ + def minimumOption[A: Order](fa: F[A]): Option[A] = + unorderedReduceOption(fa)(UnorderedFoldable.minCommutativeSemigroup) + + /** + * Find the maximum `A` item in this structure according to the `Order[A]`. + * + * @return `None` if the structure is empty, otherwise the maximum element + * wrapped in a `Some`. + * + * @see [[minimumOption]] for minimum instead of maximum. + */ + def maximumOption[A: Order](fa: F[A]): Option[A] = + unorderedReduceOption(fa)(UnorderedFoldable.maxCommutativeSemigroup) + + /** + * Find the minimum `A` item in this structure according to an `Order.by(f)`. + * + * @return `None` if the structure is empty, otherwise the minimum element + * wrapped in a `Some`. + * + * @see [[maximumByOption]] for maximum instead of minimum. + */ + def minimumByOption[A, B: Order](fa: F[A])(f: A => B): Option[A] = + minimumOption(fa)(Order.by(f)) + + /** + * Find the maximum `A` item in this structure according to an `Order.by(f)`. + * + * @return `None` if the structure is empty, otherwise the maximum element + * wrapped in a `Some`. + * + * @see [[minimumByOption]] for minimum instead of maximum. + */ + def maximumByOption[A, B: Order](fa: F[A])(f: A => B): Option[A] = + maximumOption(fa)(Order.by(f)) } object UnorderedFoldable { @@ -95,4 +139,12 @@ object UnorderedFoldable { case false => Eval.False } } + + private def minCommutativeSemigroup[A: Order]: CommutativeSemigroup[A] = new CommutativeSemigroup[A] { + override def combine(x: A, y: A): A = Order.min(x, y) + } + + private def maxCommutativeSemigroup[A: Order]: CommutativeSemigroup[A] = new CommutativeSemigroup[A] { + override def combine(x: A, y: A): A = Order.max(x, y) + } } From 4c9f803fc05e7e29733cabcd38ae01b6d4510598 Mon Sep 17 00:00:00 2001 From: Jorge Adriano Branco Aires Date: Fri, 21 Feb 2020 11:51:13 +0000 Subject: [PATCH 3/3] rm syntax _.some --- core/src/main/scala/cats/UnorderedFoldable.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 0ea47c91f1..09c1d53157 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -2,7 +2,6 @@ package cats import cats.instances.long._ import cats.kernel.{CommutativeMonoid, CommutativeSemigroup} -import cats.syntax.option._ import simulacrum.{noop, typeclass} /** @@ -16,7 +15,7 @@ import simulacrum.{noop, typeclass} unorderedFoldMap(fa)(identity) def unorderedReduceOption[A](fa: F[A])(implicit A: CommutativeSemigroup[A]): Option[A] = - unorderedFoldMap(fa)(_.some)(cats.instances.option.catsKernelStdCommutativeMonoidForOption(A)) + unorderedFoldMap(fa)(Some(_): Option[A])(cats.instances.option.catsKernelStdCommutativeMonoidForOption(A)) /** * Returns true if there are no elements. Otherwise false.