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

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.

pub fn value(counter: PNCounter) -> Int

Get the current value of the counter.

Returns the sum of positive increments minus the sum of negative decrements observed across all replicas.

Search Document