lattice_counters/pn_counter
A positive-negative counter (PN-Counter) CRDT.
Supports both increment and decrement operations by pairing two G-Counters: one tracking increments and one tracking decrements. The value is the difference between the two totals. Merge delegates to G-Counter merge on each half independently.
Example
import lattice_core/replica_id
import lattice_counters/pn_counter
let counter = pn_counter.new(replica_id.new("node-a"))
|> pn_counter.increment(10)
|> pn_counter.decrement(3)
pn_counter.value(counter) // -> 7
Types
A counter that supports both increment and decrement operations.
Internally pairs two G-Counters (positive and negative). The visible
value is g_counter.value(positive) - g_counter.value(negative).
pub opaque type PNCounter
pub type UpdateError {
NegativeDelta(Int)
}
Constructors
-
NegativeDelta(Int)
Values
pub fn decrement(counter: PNCounter, delta: Int) -> PNCounter
Decrement the counter by delta.
Adds delta to the negative G-Counter (which reduces the visible value).
delta should be a non-negative integer; the negative G-Counter is
grow-only so passing a negative value violates the invariant.
See decrement_with_delta for the delta-state variant.
pub fn decrement_with_delta(
counter: PNCounter,
delta: Int,
) -> #(PNCounter, PNCounter)
Decrement the counter by delta and return both the new state and a delta.
The returned delta is a PNCounter whose negative G-Counter contains
only this replica’s new negative count and whose positive G-Counter is
empty. Merging the delta into a remote replica via merge produces the
same observable result as merging the full new state.
pub fn from_json(
json_string: String,
) -> Result(PNCounter, json.DecodeError)
Decode a PN-Counter from a JSON string produced by to_json.
Returns Ok(PNCounter) on success, or Error(json.DecodeError) if the
input is not a valid PN-Counter JSON envelope.
pub fn increment(counter: PNCounter, delta: Int) -> PNCounter
Increment the counter by delta.
Adds delta to the positive G-Counter. delta should be a non-negative
integer; the positive G-Counter is grow-only so passing a negative value
violates the invariant.
See increment_with_delta for the delta-state variant that also returns
a small payload suitable for incremental sync (e.g. over websockets).
pub fn increment_with_delta(
counter: PNCounter,
delta: Int,
) -> #(PNCounter, PNCounter)
Increment the counter by delta and return both the new state and a delta.
The returned delta is a PNCounter whose positive G-Counter contains
only this replica’s new positive count and whose negative G-Counter is
empty. Merging the delta into a remote replica via merge produces the
same observable result as merging the full new state.
pub fn merge(a: PNCounter, b: PNCounter) -> PNCounter
Merge two PN-Counters.
Merges the positive G-Counters and negative G-Counters independently using
pairwise maximum. The result’s self_id is taken from a’s positive
G-Counter.
This operation is commutative, associative, and idempotent.
pub fn new(replica_id: replica_id.ReplicaId) -> PNCounter
Create a new PN-Counter for the given replica.
Returns a fresh counter with a zero value. Both inner G-Counters are
initialized with replica_id as their node identifier.
pub fn to_json(counter: PNCounter) -> json.Json
Encode a PN-Counter as a self-describing JSON value.
Produces an envelope with type, v (schema version), and state.
Format: {"type": "pn_counter", "v": 1, "state": {"positive": {...}, "negative": {...}}}
Use from_json to decode the result back into a PNCounter.
pub fn try_decrement(
counter: PNCounter,
delta: Int,
) -> Result(PNCounter, UpdateError)
Safely decrement the counter by delta.
Returns Error(NegativeDelta(delta)) if delta is negative.
See try_decrement_with_delta for the delta-state variant.
pub fn try_decrement_with_delta(
counter: PNCounter,
delta: Int,
) -> Result(#(PNCounter, PNCounter), UpdateError)
Safely decrement the counter by delta, returning the new state and a delta.
Returns Error(NegativeDelta(delta)) if delta is negative.
pub fn try_increment(
counter: PNCounter,
delta: Int,
) -> Result(PNCounter, UpdateError)
Safely increment the counter by delta.
Returns Error(NegativeDelta(delta)) if delta is negative.
See try_increment_with_delta for the delta-state variant.
pub fn try_increment_with_delta(
counter: PNCounter,
delta: Int,
) -> Result(#(PNCounter, PNCounter), UpdateError)
Safely increment the counter by delta, returning the new state and a delta.
Returns Error(NegativeDelta(delta)) if delta is negative.