diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 09ee27b4c6..832cfce288 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -21,7 +21,7 @@ package cats -import cats.kernel.CommutativeMonoid +import cats.kernel.{CommutativeMonoid, CommutativeSemigroup} import scala.collection.immutable.{Queue, Seq, SortedMap, SortedSet} import scala.util.Try @@ -35,6 +35,16 @@ trait UnorderedFoldable[F[_]] extends Serializable { def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = unorderedFoldMap(fa)(identity) + /** + * Reduce this unordered structure by combining its elements with a [[CommutativeSemigroup]] instance. + * + * If there are no elements, the result is `None`. + */ + def unorderedReduceOption[A: CommutativeSemigroup](fa: F[A]): Option[A] = { + val reducer = CommutativeMonoid[Option[A]] + unorderedFoldMap(fa)(a => Some(a): Option[A])(using reducer) + } + /** * Fold in a [[CommutativeApplicative]] context by mapping the `A` values to `G[B]`. combining * the `B` values using the given `CommutativeMonoid[B]` instance. diff --git a/core/src/main/scala/cats/syntax/unorderedFoldable.scala b/core/src/main/scala/cats/syntax/unorderedFoldable.scala index 2a793c2552..1313829840 100644 --- a/core/src/main/scala/cats/syntax/unorderedFoldable.scala +++ b/core/src/main/scala/cats/syntax/unorderedFoldable.scala @@ -22,6 +22,8 @@ package cats package syntax +import cats.kernel.CommutativeSemigroup + trait UnorderedFoldableSyntax extends UnorderedFoldable.ToUnorderedFoldableOps { implicit final def catsSyntaxUnorderedFoldableOps[F[_]: UnorderedFoldable, A](fa: F[A]): UnorderedFoldableOps[F, A] = new UnorderedFoldableOps[F, A](fa) @@ -66,4 +68,7 @@ final class UnorderedFoldableOps[F[_], A](private val fa: F[A]) extends AnyVal { */ def count(p: A => Boolean)(implicit F: UnorderedFoldable[F]): Long = F.count(fa)(p) + + def unorderedReduceOption(implicit A: CommutativeSemigroup[A], F: UnorderedFoldable[F]): Option[A] = + F.unorderedReduceOption(fa) } diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala index e70df54e0c..5bc1320b32 100644 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -33,6 +33,9 @@ trait UnorderedFoldableLaws[F[_]] { def unorderedFoldMapAIdentity[A, B: CommutativeMonoid](fa: F[A], f: A => B): IsEq[B] = F.unorderedFoldMapA[Id, A, B](fa)(f) <-> F.unorderedFoldMap(fa)(f) + 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 2d6ee98da9..d1d47fde86 100644 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -28,6 +28,7 @@ import Prop.* import org.typelevel.discipline.Laws import cats.kernel.CommutativeMonoid import cats.instances.boolean.* +import cats.instances.option.* trait UnorderedFoldableTests[F[_]] extends Laws { def laws: UnorderedFoldableLaws[F] @@ -53,7 +54,10 @@ trait UnorderedFoldableTests[F[_]] extends Laws { "forall is lazy" -> forAll(laws.forallLazy[A] _), "contains consistent with exists" -> forAll(laws.containsConsistentWithExists[A] _), "contains consistent with forall" -> forAll(laws.containsConsistentWithForall[A] _), - "contains all elements from itself" -> forAll(laws.containsAllElementsFromItself[A] _) + "contains all elements from itself" -> forAll(laws.containsAllElementsFromItself[A] _), + "unorderedReduceOption consistent with unorderedFold" -> forAll( + laws.unorderedReduceOptionConsistentWithUnorderedFold[A] _ + ) ) }