removeAll

fun removeAll(elements: Set<E>): Patch<ORSet<E>>

Remove every element of elements at once — and return the change: the dots currently on those elements, retired, and nothing else.

The bulk sibling of remove, and indistinguishable from calling it in a loop: the same elements gone, the same dots retired, the same retained context, the same bytes. A receiver cannot tell which was sent, because the delta this returns is exactly the join of the deltas the loop would have produced.

What differs is the cost. Absorbing a patch is a full causal join over the whole set, so elements.fold(set) { s, e -> s.piece { it.remove(e) } } pays one join per element — Θ(n·N), and at the buffer sizes this exists for that is minutes, not milliseconds. This pays one join for the whole run, and builds its context in a single pass (see DotContext.witnessing), so it is Θ(N + n log n).

As with remove, the context carries exactly the named elements' live dots — never the sender's full history. That is what keeps add-wins intact through a bulk removal: a concurrent add from a peer mints a dot this removal never witnessed, so it is absent from the delta's context and survives the join. A tempting "empty the store and keep the whole context" shortcut would look identical on the author's own replica and destroy exactly that — it is the shape ORSetDeltaMutatorLawTest keeps pinned as a negative control.

Retaining the context is also the point: it is what lets a caller drop every element while remaining dominant over a peer that re-merges its pre-removal copy, rather than watching the whole set come back. Elements that are absent are skipped, so an elements naming none of them — or an empty one — yields the lattice identity and absorbing it changes nothing.

Samples

val a = ReplicaId("A")
val b = ReplicaId("B")

var set = ORSet.empty<String>()
listOf("alice", "bob", "carol").forEach { name -> set = set.piece { it.add(a, name) } }
val snapshot = set

// One patch drops all three — the same dots the per-element loop would have retired, so one
// causal join does the work of three.
set = set.piece { it.removeAll(set.elements) }
check(set.elements.isEmpty())

// The retained context is the point: a peer re-merging its pre-removal copy stays empty
// rather than resurrecting everyone.
check(set.piece(snapshot).elements.isEmpty())

// Add-wins is untouched: B's add mints a dot the removal never witnessed, so it survives.
val concurrent = snapshot.add(b, "bob")
check(set.piece(concurrent).contains("bob"))