Repository navigation
Add minimumOption and maximumOption to UnorderedFoldable - #4911
jorgeadriano wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It alters the core Foldable/UnorderedFoldable type-class and syntax hierarchy with binary-compatibility implications, which warrants final human review.
Review effort: Balanced
Findings: None
What changed in this PR
This PR moves minimumOption and maximumOption down from Foldable to UnorderedFoldable, since finding an extremum is a commutative/unordered operation. The new defaults are implemented in terms of unorderedReduceOption (added in #4876), making extrema available for unordered structures like Set and Map and for code generically constrained to UnorderedFoldable. Foldable keeps its existing reduceLeftOption-based implementations as explicit overrides, and the syntax carrier/converter traits are re-parented so the Foldable conversion remains unambiguous. minimumByOption/maximumByOption intentionally stay on Foldable.
Changes:
- Add
minimumOption/maximumOptiondefaults and Ops syntax toUnorderedFoldable, implemented viaunorderedReduceOption; convert theFoldableequivalents tooverride. - Re-parent
Foldable.Ops→UnorderedFoldable.OpsandFoldable.ToFoldableOps→UnorderedFoldable.ToUnorderedFoldableOpsto avoid conflicting/ambiguous syntax. - Add order-consistency laws plus a new
unorderedFoldableOrderrule set, and extensive unit, syntax, and binary-compatibility fixtures.
| File | Description |
|---|---|
core/src/main/scala/cats/UnorderedFoldable.scala |
Adds minimumOption/maximumOption defaults (via unorderedReduceOption) and their Ops syntax. |
core/src/main/scala/cats/Foldable.scala |
Marks the existing extrema as override; re-parents Ops and ToFoldableOps to the unordered carriers. |
laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala |
Adds minimumOption/maximumOption consistency-with-Order laws. |
laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala |
Adds the unorderedFoldableOrder rule set wiring the new laws. |
tests/.../UnorderedFoldableSuite.scala |
Adds a syntax dispatch test and checks the new order rule set for Set/Map. |
tests/.../UnorderedFoldableSyntaxSuite.scala |
New suite exercising syntax import combinations, override dispatch, and permutation invariance. |
binCompatTest/.../FoldableMinimumMaximum.scala |
New fixtures (old-compiled instances/callers) for forward binary compatibility. |
binCompatTest/.../FoldableMinimumMaximumTest.scala |
New tests asserting old callers inherit the new defaults against current Cats. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
18c0e0e to
4eff276
Compare
Follow-up to #4876. Closes #2715 and #3309.
Summary
minimumOptionandmaximumOptioncurrently requireFoldable, although finding an extremum does not require an ordered traversal. This PR adds both methods toUnorderedFoldable, making them available for unordered structures such asSetandMap, and for generic code constrained only byUnorderedFoldable.The new defaults are implemented in terms of
unorderedReduceOption. They returnNonefor empty structures; if several elements compare as equal according toOrder[A], any of them may be returned.minimumByOptionandmaximumByOptionremain onFoldable: when distinctAvalues map to equal ordered keys, the key order does not provide an equality onAthat would make choosing one representative an unordered operation.Existing
Foldable.minimumOption/maximumOptionimplementations are retained as explicit overrides, so ordered structures keep their existing reduction path. No new abstract method is added toUnorderedFoldableinstances.Syntax and compatibility
The existing extrema declarations on both
FoldableandFoldable.Opsare retained to preserve their binary entry points. The syntax hierarchy also becomes:These relationships solve two separate source-compilation issues in this design:
Opsinheritance,Foldable.AllOpsinherits competing concrete extrema definitions from the ordered and unordered syntax carriers.toFoldableOpsandtoUnorderedFoldableOps. Relating the owners gives theFoldableconversion the required specificity.This explains why both relationships are present in the chosen design. One small source-surface consequence is that the deprecated
Foldable.nonInheritedOpsalso inherits unordered syntax despite its historical name.Compiler evidence / reproduction
Each experiment starts independently from a clean checkout of the PR and changes one declaration in
Foldable.scala.A.
Foldable.OpsTemporarily change:
to:
Run:
Compilation fails because the retained extrema methods no longer override the unordered carrier, while
Foldable.AllOpsinherits competing concrete extrema definitions. The diagnostics includeminimumOption overrides nothing,maximumOption overrides nothing, and conflicting members inAllOps.Restoring the original parent and rerunning the same command passes. A reduced carrier-only model reproduces the concrete-member conflict on Scala 2.12.21, 2.13.18, and 3.3.8.
B.
ToFoldableOpsKeep the working
Opshierarchy and temporarily change:to:
Run:
Core compilation passes, but generic syntax in the laws/tests fails with ambiguous
toFoldableOps/toUnorderedFoldableOpsconversions. A minimal example is:The same ambiguity was reproduced for
size,exists,minimumOption, andmaximumOptionon Scala 2.12.21, 2.13.18, and 3.3.8. Restoring the owner inheritance makes the same sources compile; typed trees selecttoFoldableOps.These experiments cover source compilation only; binary compatibility is checked separately.
Tests and validation
Coverage includes:
Order;SetandMap, including tied values where anyOrder-equal minimum/maximum may be returned;unorderedFoldable,foldable, combined imports, andcats.syntax.all, including strongerTraverseandReduciblecontexts;Foldableextrema implementations for ordered types;Foldable.Ops/AllOpscallers, and oldUnorderedFoldableimplementations using the new default methods.The focused syntax suite passes on Scala 2.12.21, 2.13.18, and 3.3.8. MiMa and the old-client compatibility fixtures pass, as do the broader JVM / Scala.js checks and the affected Scala Native checks.