diff --git a/CLAUDE.md b/CLAUDE.md index c98c380..e99a365 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,6 +43,13 @@ All types implement `json.Marshaler`/`json.Unmarshaler` and `sql.Scanner`. The ` When bumping the minimum Go version, the commit message should include `#minor` to trigger a minor version bump. +## Conventions + +- **Zero-value style**: Use `var x T` instead of `x := T{}`; refer to this as "T's zero value" in prose. +- **Tests are the spec**: When modifying implementations, do not change tests. Treat test failures as implementation bugs. +- **Mathematical correctness**: Prefer mathematically correct semantics (e.g., vacuous truth for empty predicates). +- **Commit messages**: Use conventional commits (`feat:`, `fix:`, `docs:`, etc.). + ## Testing Tests use property-based state machine testing via `pgregory.net/rapid`. The state machine in `set_test.go` validates invariants across all set implementations. Tests run in parallel. diff --git a/examples_test.go b/examples_test.go index f5031eb..afdf046 100644 --- a/examples_test.go +++ b/examples_test.go @@ -312,6 +312,10 @@ func ExampleContainsSeq() { ints.Add(3) ints.Add(2) + if ContainsSeq(ints, slices.Values([]int{})) { + fmt.Println("Non-empty set contains empty sequence") + } + if ContainsSeq(ints, slices.Values([]int{3, 5})) { fmt.Println("3 and 5 are present") } @@ -321,6 +325,7 @@ func ExampleContainsSeq() { } // Output: // Empty set contains empty sequence + // Non-empty set contains empty sequence // 3 and 5 are present // 6 is not present } diff --git a/map.go b/map.go index 8322f3f..0e67274 100644 --- a/map.go +++ b/map.go @@ -59,7 +59,7 @@ func (s *Map[M]) Clear() int { // Add an element to the set. Returns true if the element was added, false if it was already present. func (s *Map[M]) Add(m M) bool { - if s.Contains(m) { + if _, ok := s.set[m]; ok { return false } s.set[m] = struct{}{} @@ -68,7 +68,7 @@ func (s *Map[M]) Add(m M) bool { // Remove an element from the set. Returns true if the element was removed, false if it was not present. func (s *Map[M]) Remove(m M) bool { - if !s.Contains(m) { + if _, ok := s.set[m]; !ok { return false } delete(s.set, m) diff --git a/set.go b/set.go index 75c5d4b..2a0fbae 100644 --- a/set.go +++ b/set.go @@ -145,19 +145,22 @@ func Equal[K comparable](a, b Set[K]) bool { if a.Cardinality() != b.Cardinality() { return false } - return Subset(a, b) && Subset(b, a) + for k := range a.Iterator { + if !b.Contains(k) { + return false + } + } + return true } -// ContainsSeq returns true if the set contains all elements in the sequence. Empty sets are considered to contain only empty sequences. +// ContainsSeq returns true if the set contains all elements in the sequence. Returns true for an empty sequence (vacuous truth). func ContainsSeq[K comparable](s Set[K], seq iter.Seq[K]) bool { - noitems := true for k := range seq { - noitems = false if !s.Contains(k) { return false } } - return (s.Cardinality() == 0 && noitems) || !noitems + return true } // Disjoint returns true if the two sets have no elements in common.