feat(tableaupb): add Fraction.MatchAny/MatchAll, rename Cmp to Match#437
Conversation
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>
|
The latest Buf updates on your PR. Results from workflow Buf CI / buf (pull_request).
|
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
Kybxd
left a comment
There was a problem hiding this comment.
Review Summary
Required change
MatchAnyshould use a loop: Currently it usesslices.ContainsFunc(cmps, f.Match). Please switch to an explicitforloop to matchMatchAll(see the inlinesuggestion). This keeps the two helpers consistent and lets you remove the now-unusedslicesimport (otherwisego buildfails).
Other optimization points
-
Cmp→Matchis a breaking change: You kept a// Deprecated:wrapper forCompare, butFraction.Cmpwas renamed outright with no compatibility alias. Since this package is published, external callers ofFraction.Cmpwill fail to compile. Suggest either:- keeping a
// Deprecated: Use Match insteadCmpalias (consistent with howCompareis 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.
- keeping a
-
(Optional, non-blocking) nil safety:
Compare(nil, cmp)andf.Match(cmp)will panic whenFractionorcmpis nil. This is pre-existing behavior, but sinceMatchAny/MatchAllare more "entry-point" style APIs, a nil guard could make them safer. Could be a follow-up. -
(Optional, non-blocking) test coverage:
TestMatchcoversMatchbut not the deprecatedComparewrapper path — adding a trivial case prevents the deprecated shim from silently rotting. Also consider a panic test for an unknownComparator_Signto 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) |
There was a problem hiding this comment.
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.
| 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".
There was a problem hiding this comment.
But slices.ContainsFunc is prefered, see https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#hdr-Analyzer_slicescontains
- 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>
Summary
Adds two multi-comparator helpers on
*Fractionand renames the existing single-comparator method for a consistent naming family.Fraction.Match(cmp)Cmp)Fraction.MatchAny(cmps...)false(matchesslices.ContainsFunc)Fraction.MatchAll(cmps...)true(vacuous truth — identity element for AND)The package-level
Compareis kept as a thin// Deprecated:wrapper overMatchso external importers don't break, with a clear migration pointer.Why
Matchinstead ofCmp/CompareThe singular method was renamed from
Cmp→Match, and the new plural APIs use the same verb. The reasons:Compare/Cmpis a reserved Go idiom that returns anintordering (−1/0/+1). Across the standard library —bytes.Compare,strings.Compare,bytes.(*Buffer).Cmp,cmp.Compare, and theslices.SortFunccomparator — the name implies an integer ordering result. Abool-returning function namedCompare/Cmpmisleads readers into expecting anint, and obscures whyCmpis even usable as aslices.ContainsFuncpredicate (it only type-checks because it returnsbool).The operation is a predicate test, not a peer-to-peer comparison.
Compare/Cmpimplies comparing two peers of the same kind. Here the operands are asymmetric: aFraction(a value) tested against aComparator(a constraint with a sign + threshold). The signature itself betrays this — you can't even "compare" twoFractions, only test aFractionagainst aComparator.Matchis an established Gobool-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 previousCompare(func) vsCmp(method) split.Empty-list semantics
MatchAnyreturnsfalsefor an empty list — consistent withslices.ContainsFunc(nil, pred)and the identity element for OR.MatchAllreturnstruefor an empty list — vacuous truth, the identity element for AND (consistent with Pythonall([])and JS[].every()). This keeps the pair composable and De Morgan-consistent:Breaking change
Fraction.Cmp(method) andtableaupb.Compare(package func) are exported on a published package.Compareis preserved as a deprecated wrapper (no in-repo callers remain, sostaticcheckSA1019 stays clean).Cmpis renamed outright — this is a breaking change for any external code callingFraction.Cmp. Within this repo there are zero such callers (verified by grep).Verification
go build ./...go vet ./...go test ./proto/tableaupb/—TestMatch,TestMatchAny,TestMatchAllall pass🤖 Generated with Claude Code