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.

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