01 · Problem
What is it for?
Names such as difference become ambiguous unless the direction and mutation behavior are explicit.
02 · Mental model
How should you think about it?
Set membership defines the operation; a new set and a changed receiver are different contracts.
Demonstrates union, intersection and the existing directional difference contract. except computes argument minus receiver. The direction is retained and regression-tested.
03 · Control flow
What happens in which order?
- Choose the left and right sets.
- Evaluate membership for each candidate.
- Add matching values to a new result.
- Mutate the receiver only for documented union operations.
04 · Building blocks
The important contracts
merge / both / except / filterPerform union, intersection, argument-minus-receiver difference and filtering.05 · Build it yourself
Implement a small core
Start with this small independent implementation, then use the steps below to make its contract explicit.
- Choose the left and right sets.
- Evaluate membership for each candidate.
- Add matching values to a new result.
- Mutate the receiver only for documented union operations.
class SetOps<T> extends Set<T> {
union(other: Iterable<T>) {
return new SetOps([...this, ...other]);
}
intersection(other: ReadonlySet<T>) {
return new SetOps([...this].filter(value => other.has(value)));
}
difference(other: ReadonlySet<T>) {
return new SetOps([...this].filter(value => !other.has(value)));
}
}06 · Verify
What should you test?
- For A={1,2}, B={2,3}, assert A.except(B)={3}.
- Check intersection, union and empty inputs.
- Verify filtering does not mutate its source.
Try it yourself
Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.
For A={1,2}, B={2,3}, assert A.except(B)={3}.
Compare your approach
Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.
07 · Limits
Trade-offs and failure modes
- Object membership compares references.
- except has an unusual direction retained for compatibility.
- Result types differ between methods.
except computes argument minus receiver. The direction is retained and regression-tested.
08 · Keep exploring
Source code and related ideas
yarn tsx packages/better-set/examples/basic.ts