Add unorderedReduceOption to UnorderedFoldable#4876
Conversation
There was a problem hiding this comment.
Pull request overview
Adds unorderedReduceOption to UnorderedFoldable to support reducing possibly-empty unordered structures, with accompanying syntax and law/test coverage to ensure consistency with unorderedFold.
Changes:
- Add
UnorderedFoldable#unorderedReduceOptionimplemented viaunorderedFoldMapoverOption[A]. - Add syntax support (
fa.unorderedReduceOption) undercats.syntax.all._. - Add a new law + discipline wiring, plus a focused suite test.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| core/src/main/scala/cats/UnorderedFoldable.scala | Adds the new unorderedReduceOption API on the typeclass. |
| core/src/main/scala/cats/syntax/unorderedFoldable.scala | Exposes unorderedReduceOption as syntax on F[A]. |
| laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala | Adds a law relating unorderedReduceOption to unorderedFold/isEmpty. |
| laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala | Wires the new law into the existing unorderedFoldable RuleSet. |
| tests/shared/src/test/scala/cats/tests/UnorderedFoldableSuite.scala | Adds a direct property test for unorderedReduceOption. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def unorderedReduceOption[A](fa: F[A])(implicit A: CommutativeSemigroup[A]): Option[A] = | ||
| unorderedFoldMap(fa)(a => Some(a): Option[A])( | ||
| cats.kernel.instances.option.catsKernelStdCommutativeMonoidForOption | ||
| ) |
|
|
||
| def unorderedReduceOption(implicit A: CommutativeSemigroup[A], F: UnorderedFoldable[F]): Option[A] = | ||
| F.unorderedReduceOption(fa) |
|
Thank you for the PR! The change makes sense to me. A few rather minor comments from my side are inline below. |
| def unorderedReduceOption[A](fa: F[A])(implicit A: CommutativeSemigroup[A]): Option[A] = | ||
| unorderedFoldMap(fa)(a => Some(a): Option[A])( | ||
| cats.kernel.instances.option.catsKernelStdCommutativeMonoidForOption | ||
| ) |
There was a problem hiding this comment.
def unorderedReduceOption[A: CommutativeSemigroup](fa: F[A]): Option[A] = {
val reducer = CommutativeMonoid[Option[A]]
unorderedFoldMap(fa)(a => Some(a): Option[A])(using reducer)
}implicit Aonly makes sense if the code needs to refer toAdirectly. It is not the case here, so I'd suggest using the context bounds syntax, as it seems to be more concise and readable.- Referring to
catsKernelStdCommutativeMonoidForOptiondirectly is rather brittle and doesn't seem necessary. I'd suggest to summon the required instance and pass it tounorderedFoldMap.
| test(s"UnorderedFoldable[$name].unorderedReduceOption") { | ||
| forAll { (fa: F[Int]) => | ||
| implicit val F: UnorderedFoldable[F] = instance | ||
| val expected = | ||
| if (instance.isEmpty(fa)) None | ||
| else Some(instance.unorderedFold(fa)) | ||
| assert(fa.unorderedReduceOption === expected) | ||
| } | ||
| } |
There was a problem hiding this comment.
Is this test somehow different from the unorderedReduceOptionConsistentWithUnorderedFold check in UnorderedFoldableLaws? If not, then we can probably drop this one in order to avoid bloating tests with identical checks.
Add
unorderedReduceOptiontoUnorderedFoldableMotivation
Foldablehas ordered reduction methods such asreduceLeftOptionandreduceRightOption,but
UnorderedFoldabledoes not currently have an unordered equivalent for reducing apossibly-empty structure.
This PR adds
unorderedReduceOption, which reduces an unordered structure toOption[A],returning
Nonefor empty structures andSome(combined)for non-empty ones.The constraint is
CommutativeSemigroup[A], rather thanSemigroup[A], becauseUnorderedFoldableprovides no guarantee on element order.This is related to the discussion in #2715 and extracts the focused
unorderedReduceOptionpart of #3309. Unlike #3309, this PR intentionally leaves
minimumOption,maximumOption,minimumByOption, andmaximumByOptionout of scope, since those raise separate API andbinary-compatibility considerations, as also discussed around #3132.
API
Also available as syntax via
cats.syntax.all._:Implementation
The implementation is defined in terms of
unorderedFoldMap, by lifting eachAintoOption[A]and folding with the existingCommutativeMonoid[Option[A]]instance derivedfrom
CommutativeSemigroup[A].Law
The PR adds a law checking consistency with
unorderedFold:The law is wired into the existing
UnorderedFoldableTests.unorderedFoldablerule set.Eq[Option[A]]is resolved internally from the existingEq[A]instance viacats.instances.option, so the public signature ofunorderedFoldableis unchanged.Included changes
UnorderedFoldable#unorderedReduceOptioncats.syntax.all._unorderedFoldUnorderedFoldableSuite