CX · Reference guideComputation identity
PlaygroundDownloadsAboutv0.17.0

Computation identity

Naming a computation, not just a value

Ring 0 gives every value an address. Ring 1 extends the same move to computations: a pure function applied to known inputs in a known environment is itself a value — a record — and that record's content address names the computation. Same function, same inputs, same environment: same address, everywhere, forever.

The record has four slots, and each earns its place:

Slot What it carries Why it is in the hash
fn the definition — its code-equivalence key for cache sharing, plus the content address of the exact source text for trust a different definition is a different computation
inputs the content address of every argument reformatting an input does not move it; changing a field does
env runtime version, builtin-set identity, schema dialect a runtime patch can fix a determinism bug — and a fix must invalidate
caps the capability set in canonical form recorded for completeness — for a pure computation it provably cannot change the result

What this buys: a node that holds neither the source nor the inputs can serve a cached result by address, and the requester can re-derive the address to verify what it was handed. A reformatted input is a cache hit (same address); a runtime upgrade is a cache miss (new address, cold cache, never a wrong answer). The environment slot is deliberately minimal and only ever grows additively — when a field is added, every address changes exactly once, and the old cache goes cold rather than stale.

The cache is pure-only and fail-loud: constructing a computation record for an impure function is a typed error, and results live in an ordinary store namespace — an explicit lookup, not an invisible memoization layer. Budget exhaustion and non-termination are never cached. Impure work can still be made replayable by recording its nondeterministic boundary reads onto a tape and treating the tape as one more addressed input — determinism manufactured after the fact, with the same identity machinery.

Determinism and reproducibility

Computation identity only works because CX evaluation is deterministic where it claims to be. The same input produces byte-identical output across machines, operating systems, architectures, and bindings — the foundation under content-addressing, reproducible builds, cross-binding parity tests, and audit trails.

  • Canonical form is the substrate. Attribute order, whitespace, quoting, and numeric formatting are pinned by the canonical rules (see identity), so equality and hashing never depend on who serialized the value.
  • Float formatting is CX's own. Canonical float output uses a self-contained shortest-round-trip algorithm, not the host printf — libc differences (musl, glibc, Apple) cannot leak into canonical bytes.
  • Arithmetic is IEEE-754 strict. Round-to-nearest-even, no fast-math, bit-identical exact arithmetic across platforms. Transcendentals (sin, log, …) use the host libm and are documented as platform-dependent — keep them out of anything whose address you rely on.
  • No locale leaks. Pure builtins are locale-free by rule: case mapping is Unicode default casing, never the host locale. A locale-sensitive pure builtin is treated as a conformance bug, not a platform quirk.

What is deliberately not deterministic, and flagged as such: effectful builtins that read the world (now, random, uuid — each behind its capability, see capabilities); wall-clock timeout behavior under [?timeout]; and the timing of parallel execution (though never its result — next subsection).

pure means deterministic — including under [par]

CX's purity checker classifies every definition, and the guarantee is normative: a pure computation is deterministic — same inputs, same environment, same result, regardless of capability set, thread count, or evaluation order. This is the theorem that makes a computation address meaningful at all.

The clause that historically breaks this promise in other systems is parallelism, so CX pins it explicitly: [par] reassembles results in source order, always. A parallel [?for], [?map], or [?reduce] may execute iterations in any order across any number of threads, but the value it returns is the value the sequential evaluation returns — element for element, float-accumulation order included. There is no unordered mode. (An older [ordered] clause survives grammatically as a documented no-op: order is no longer optional, so there is nothing left for it to request.)

      [?map (1, 2, 3, 4) [using [?fn ($x) [* $x $x]]] [par]]
    
      (1, 4, 9, 16)
    

Parallelism by construction

Because every value is immutable and every error is a value, parallelism costs almost nothing to adopt. [par] on [?for], [?map], and [?reduce] splits pure work across a bounded worker pool; there is no user-level lock primitive because there is no shared mutable state to protect.

      [# Comprehension over matches: ]
         [?for [in $r //request] [par] [yield [got [$text $r]]]]

         [# Associative parallel fold: ]
         [?reduce (1, 2, 3) [using [?fn ($a $b) [+ $a $b]]] [init 0] [par]]
    

Why it is safe. No CX expression can mutate a value another thread is reading — [?modify] returns a new tree, and derived values share structure with their origins by pointer (see runtime-representation), so handing a large document to N workers is handing N pointers. Purity is the parallelization license: the evaluator parallelizes bodies the checker proves pure, and the source-order reassembly rule (above) guarantees the result is indistinguishable from sequential evaluation.

When [par] does not help. Tiny bodies lose to dispatch overhead — for a trivial body over a small input, the cost of distributing the work can exceed the work itself, so sequential evaluation wins. An erroring iteration cancels its in-flight siblings cooperatively: iterations observe cancellation at safepoints (between CXPath steps and directive invocations), so a long CPU-bound body without natural safepoints runs to completion — the same hazard as every cooperative-cancellation system. Error values from iterations propagate like any other value; see the error material in code.