lattice/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/pn_counter
let counter = pn_counter.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).
The type is non-opaque so that serialization can access the inner
G-Counter fields; do not rely on internal field names in application code.
pub type PNCounter {
PNCounter(
positive: g_counter.GCounter,
negative: g_counter.GCounter,
)
}
Constructors
-
PNCounter( positive: g_counter.GCounter, negative: g_counter.GCounter, )
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.
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.
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: String) -> 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.