Skip to content

feat(tableaupb): add Fraction.MatchAny/MatchAll, rename Cmp to Match#437

Merged
wenchy merged 2 commits into
masterfrom
feat/fraction-match-any-all
Jul 13, 2026
Merged

feat(tableaupb): add Fraction.MatchAny/MatchAll, rename Cmp to Match#437
wenchy merged 2 commits into
masterfrom
feat/fraction-match-any-all

Conversation

@wenchy

@wenchy wenchy commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

Adds two multi-comparator helpers on *Fraction and renames the existing single-comparator method for a consistent naming family.

API Logic Empty list
Fraction.Match(cmp) single comparator (renamed from Cmp) n/a
Fraction.MatchAny(cmps...) OR false (matches slices.ContainsFunc)
Fraction.MatchAll(cmps...) AND true (vacuous truth — identity element for AND)

The package-level Compare is kept as a thin // Deprecated: wrapper over Match so external importers don't break, with a clear migration pointer.

Why Match instead of Cmp / Compare

The singular method was renamed from CmpMatch, and the new plural APIs use the same verb. The reasons:

  1. Compare/Cmp is a reserved Go idiom that returns an int ordering (−1/0/+1). Across the standard library — bytes.Compare, strings.Compare, bytes.(*Buffer).Cmp, cmp.Compare, and the slices.SortFunc comparator — the name implies an integer ordering result. A bool-returning function named Compare/Cmp misleads readers into expecting an int, and obscures why Cmp is even usable as a slices.ContainsFunc predicate (it only type-checks because it returns bool).

  2. The operation is a predicate test, not a peer-to-peer comparison. Compare/Cmp implies comparing two peers of the same kind. Here the operands are asymmetric: a Fraction (a value) tested against a Comparator (a constraint with a sign + threshold). The signature itself betrays this — you can't even "compare" two Fractions, only test a Fraction against a Comparator.

  3. Match is an established Go bool-verb (regexp.MatchString) that reads as a test at both the method (f.Match(cmp)) and package-func levels. It also unifies the singular and plural under one verb (Match / MatchAny / MatchAll), instead of the previous Compare (func) vs Cmp (method) split.

Empty-list semantics

  • MatchAny returns false for an empty list — consistent with slices.ContainsFunc(nil, pred) and the identity element for OR.
  • MatchAll returns true for an empty list — vacuous truth, the identity element for AND (consistent with Python all([]) and JS [].every()). This keeps the pair composable and De Morgan-consistent:
    MatchAll(left, append(cmps, c)) == MatchAll(left, cmps) && left.Match(c)   // holds iff empty == true
    MatchAny(left, append(cmps, c)) == MatchAny(left, cmps) || left.Match(c)   // holds iff empty == false
    

Breaking change

Fraction.Cmp (method) and tableaupb.Compare (package func) are exported on a published package. Compare is preserved as a deprecated wrapper (no in-repo callers remain, so staticcheck SA1019 stays clean). Cmp is renamed outright — this is a breaking change for any external code calling Fraction.Cmp. Within this repo there are zero such callers (verified by grep).

Verification

  • go build ./...
  • go vet ./...
  • go test ./proto/tableaupb/TestMatch, TestMatchAny, TestMatchAll all pass

🤖 Generated with Claude Code

Add two multi-comparator helpers on *Fraction:
- MatchAny(cmps...): OR logic, returns false for an empty list
  (matches slices.ContainsFunc semantics).
- MatchAll(cmps...): AND logic, returns true for an empty list
  (vacuous truth, the identity element for AND).

Also rename Fraction.Cmp to Match for a consistent naming family
(Match / MatchAny / MatchAll), and deprecate the package-level
Compare as a thin wrapper over Match.

Why Match instead of Cmp/Compare:
- Go's Compare/Cmp idiom returns an int ordering (-1/0/+1), e.g.
  bytes.Compare, strings.Compare, bytes.(*Buffer).Cmp, cmp.Compare,
  and the slices.SortFunc comparator. A bool-returning function named
  Compare/Cmp misleads readers into expecting an int, and obscures why
  Cmp is even usable as a slices.ContainsFunc predicate.
- The operation is a predicate test (does this value satisfy this
  constraint), not a peer-to-peer comparison: the operands are
  asymmetric (a Fraction value vs a Comparator constraint).
- Match is an established Go bool-verb (regexp.MatchString) that reads
  as a test at both the method (f.Match(cmp)) and package-func levels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf CI / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJul 10, 2026, 12:28 PM

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 75.60%. Comparing base (445fda5) to head (15caf41).

Files with missing lines Patch % Lines
proto/tableaupb/wellknown_util.go 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #437      +/-   ##
==========================================
+ Coverage   75.58%   75.60%   +0.01%     
==========================================
  Files          88       88              
  Lines        9520     9527       +7     
==========================================
+ Hits         7196     7203       +7     
  Misses       1749     1749              
  Partials      575      575              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Kybxd

This comment was marked as duplicate.

@Kybxd Kybxd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Required change

  1. MatchAny should use a loop: Currently it uses slices.ContainsFunc(cmps, f.Match). Please switch to an explicit for loop to match MatchAll (see the inline suggestion). This keeps the two helpers consistent and lets you remove the now-unused slices import (otherwise go build fails).

Other optimization points

  1. CmpMatch is a breaking change: You kept a // Deprecated: wrapper for Compare, but Fraction.Cmp was renamed outright with no compatibility alias. Since this package is published, external callers of Fraction.Cmp will fail to compile. Suggest either:

    • keeping a // Deprecated: Use Match instead Cmp alias (consistent with how Compare is handled), or
    • clearly documenting the method rename as a breaking change in the release notes.
      This is a design trade-off, but worth considering for a published API.
  2. (Optional, non-blocking) nil safety: Compare(nil, cmp) and f.Match(cmp) will panic when Fraction or cmp is nil. This is pre-existing behavior, but since MatchAny/MatchAll are more "entry-point" style APIs, a nil guard could make them safer. Could be a follow-up.

  3. (Optional, non-blocking) test coverage: TestMatch covers Match but not the deprecated Compare wrapper path — adding a trivial case prevents the deprecated shim from silently rotting. Also consider a panic test for an unknown Comparator_Sign to lock in the existing contract.

Overall the implementation and tests are solid. Mainly #1 needs changing, and please confirm the handling of the breaking change in #2.

// MatchAny reports whether the fraction matches any of the given comparators (OR logic).
// It returns false if no comparators are provided.
func (f *Fraction) MatchAny(cmps ...*Comparator) bool {
return slices.ContainsFunc(cmps, f.Match)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please align MatchAny with MatchAll by using an explicit loop instead of slices.ContainsFunc. This keeps the two helpers stylistically consistent and lets us drop the now-unused slices import.

Suggested change
return slices.ContainsFunc(cmps, f.Match)
func (f *Fraction) MatchAny(cmps ...*Comparator) bool {
for _, cmp := range cmps {
if f.Match(cmp) {
return true
}
}
return false
}

Also remove import "slices".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- Add a doc comment to Match describing the relational contract and why
  comparison uses cross-multiplication: exact integer arithmetic gives
  both performance (no division, no FP) and accuracy (no rounding on
  large int32 values that a/b-vs-c/d float comparison would risk).
- Replace the default panic with returning false for an unknown
  Comparator_Sign. Proto enums are open-ended: a deserialized message
  can carry a sign a newer producer added. As a predicate feeding
  validation, Match degrades to "not satisfied" rather than crashing the
  load path — consistent with other enum-switch defaults in the repo
  (e.g. log/core/level.go) and with protovalidate's unknown-enum
  semantics.
- Add an "unknown sign" test case pinning the new contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wenchy
wenchy merged commit 1c5fee6 into master Jul 13, 2026
9 checks passed
@wenchy
wenchy deleted the feat/fraction-match-any-all branch July 13, 2026 02:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants