Why CX
CX starts from a single axiom: computing is data transformation, nothing more. Languages, frameworks and processes are ceremony around that fact. CX is the principle made a language: transform.
We built CX so that the data is the program's own shape.
One bracketed syntax is the document, the query, the program and the
compiler's own tree, and everything we promise below follows from
holding that line: numbers that stay exact, effects you grant rather
than inherit, errors and absence that are values, patterns that cannot
blow up, dispatch on the shape of the data. Each claim on this page is
a pair of conformance cases this guide's cx replayed when the page
was built — the form that fails on the left, ours on the right. If a
claim ever stops holding, this page stops building.
We keep exact numbers exact
A fraction literal is a decimal — exact, base-10, arbitrary precision — and arithmetic keeps it one. Money stays money. Mixing a decimal with a binary float is refused out loud, naming the one bridge between them, rather than rounded in silence into a column that was never supposed to round.
Before — the form that fails
Mixing a decimal literal with a float in arithmetic
prog.cx
[+ 1.5 1.0e0]
$ cx prog.cx
[err code=cx-err:CXER0100 message='+: decimal/bigint arithmetic admits only int/bigint/decimal operands — [cast] is the only decimal↔float bridge (L44)' at='1:1']
Fixture ap-decimal-float-mix-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
Keep exact quantities in decimals
prog.cx
[?let [= $subtotal 19.99] [= $shipping 5.01] [total [+ $subtotal $shipping]]]
$ cx prog.cx
[total 25.00]
Fixture ap-decimal-float-mix-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
We grant effects; nothing is inherited
A program reads nothing, writes nothing, dials nothing and reads no clock until the command line grants it. The refusal is an ordinary value that names the flag which would allow the effect — not a crash, not a silent empty result — so the same program is safe to run first and to trust second. See capabilities for the whole grant surface.
Before — the form that fails
Reaching for a file with no grant on the command line
prog.cx
[?lib 'cx-stdlib/io' as=io]
[probe exists=[$io:exists '/']]
$ cx prog.cx
[err code=cx-err:CXER0271 message='E_CAP_DENIED: read capability required for io-exists; none granted (grant via --allow-read)' at='2:15']
Fixture ap-effect-ungranted-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
The same program, the read granted before the file
prog.cx
[?lib 'cx-stdlib/io' as=io]
[probe exists=[$io:exists '/']]
$ cx --allow-read prog.cx
[probe exists=true]
Fixture ap-effect-granted-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
Our errors are values you match on
There is no try. An [err] is a value that flows out of whatever
is built from it, and the shape you wanted is a pattern you match
against: the single-arm [?match] destructures or refuses with the
language's own diagnostic, so a bespoke error code and its wording
stop being yours to maintain. The multi-arm form is for genuinely
several shapes, not for one shape and a safety net.
Before — the form that fails
Multi-arm [?match] with one real arm and an else that re-raises
prog.cx
[?match (3, 4, 9)
[case ($b, $a, *$rest) [+ $a $b]]
[else [err code='bad-stack' message='need at least two operands']]]
$ cx prog.cx
7
Fixture ap-match-else-rethrow-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
Single-arm [?match] — destructure or refuse
prog.cx
[?match (3, 4, 9) ($b, $a, *$rest) [yield [+ $a $b]]]
$ cx prog.cx
7
Fixture ap-match-else-rethrow-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
Absence is a value we can ask about
A missing key, an out-of-range read, an empty match set — each has a
defined, distinguishable answer, and absence is not emptiness.
[$present] asks whether something is there at all; [$exists]
asks whether it has contents. A guard that confuses the two takes
the wrong branch at exit 0 with nothing printed, which is the one
outcome we refuse to ship; the right predicate makes the question
explicit.
Before — the form that fails
[$exists] as a presence test — false for the element [$first] just returned
prog.cx
[?let [= $d [grants [grant verb=read to=alice] [grant verb=write to=bob]]]
[= $g [$first $d//grant]]
[?if [$exists $g]
[then [$string $g@verb]]
[else 'no grants declared']]]
$ cx prog.cx
'no grants declared'
Fixture ap-exists-as-presence-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
[$present] — the presence test that does not look inside
prog.cx
[?let [= $d [grants [grant verb=read to=alice] [grant verb=write to=bob]]]
[= $g [$first $d//grant]]
[?if [$present $g]
[then [$string $g@verb]]
[else 'no grants declared']]]
$ cx prog.cx
read
Fixture ap-exists-as-presence-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
Our regular expressions cannot blow up
re is RE2: a pattern that compiles runs in time linear in its
input, whatever the input. The constructs that would need
backtracking — a backreference, a lookaround — are refused when the
pattern compiles, so a hostile string can never turn a match into a
denial of service. The nested quantifier that kills a backtracking
engine answers here in linear time.
Before — the form that fails
A backreference — the construct only a backtracking engine can run
prog.cx
[?lib 'cx-stdlib/re' as=re]
[$re:compile '(a+)+\\1']
$ cx prog.cx
[err code=cx-err:CXER3200 message='E_RE_FEATURE_UNSUPPORTED: CXER3200:E_RE_FEATURE_UNSUPPORTED: (a+)+\1' at='2:1']
Fixture ap-regex-backtracking-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
The nested quantifier alone — linear on the input that kills a backtracker
prog.cx
[?lib 'cx-stdlib/re' as=re]
[matched value=[$re:matches [$re:compile '^(a+)+$'] 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab']]
$ cx prog.cx
[matched value=false]
Fixture ap-regex-linear-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
We dispatch on the shape of the data
A closed set of values is declared where it travels, in a schema, and
a [?match] over it says so with [of TypeName]: the linter then
names a missing member, an out-of-set value refuses at the boundary,
and [else] goes back to meaning only what it says. Bare strings and
a catch-all arm report a member you forgot as unknown, at exit 0,
forever.
Before — the form that fails
Dispatching a closed set as bare strings, with [else] as the safety net
prog.cx
[?match 'pending'
[case 'ok' 'done']
[case 'err' 'failed']
[else 'unknown']]
$ cx prog.cx
unknown
Fixture ap-open-string-dispatch-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
[?match … [of TypeName]] — the closed set declared where it travels
prog.cx
[?cx schema-inline
[schema of=job]
[type status::atom [enum :ok :err :pending]]]
[?match :pending [of status]
[case :ok 'done']
[case :err 'failed']
[case :pending 'waiting']]
$ cx prog.cx
waiting
Fixture ap-open-string-dispatch-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
We bind flat — readable on day one
[?let] is sequential: each binding sees the ones before it, so
three dependent bindings are one form, not three nested scopes. The
two halves below are output-identical; the flat one is the house
form, and it is the one a reader parses at a glance.
Before — the form that fails
Nested [?let] (the Scheme let* habit)
prog.cx
[?let [= $a 1]
[?let [= $b [+ $a 1]]
[?let [= $c [+ $b 1]]
[row a=$a b=$b c=$c]]]]
$ cx prog.cx
[row a=1 b=2 c=3]
Fixture ap-nested-let-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
Flat multi-binding [?let]
prog.cx
[?let [= $a 1] [= $b [+ $a 1]] [= $c [+ $b 1]]
[row a=$a b=$b c=$c]]
$ cx prog.cx
[row a=1 b=2 c=3]
Fixture ap-nested-let-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
Our operators are values — batteries included
An operator with holes is the operator's value: [+ _ _] goes
wherever a callable goes — a [using …] clause, a higher-order
standard-library parameter, an entry in an operator table a runtime
token looks up. Nothing stands between the operator and the slot, and
the arity is visible in the holes. The adapter lambda is pure
ceremony, and we do not make you write it.
Before — the form that fails
Wrapping an operator in a [?fn] to pass it somewhere
prog.cx
[?reduce (1, 2, 3, 4) [using [?fn ($a $b) [+ $a $b]]] [init 0]]
$ cx prog.cx
10
Fixture ap-adapter-lambda-wrong in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
After — the CX form
The operator value itself
prog.cx
[?reduce (1, 2, 3, 4) [using [+ _ _]] [init 0]]
$ cx prog.cx
10
Fixture ap-adapter-lambda-right in conformance/llm/antipatterns.cxd — replayed against this cx when the page was built.
Where this leads
Every module we ship is declared once, with its ring and its repository, in a registry the build holds the tree to — the figure on rings is drawn from that registry when the page is built, and so are the counts on the front page. From here: quickstart runs a first file, a first conversion, a first query and a first schema in five minutes; rings says how much of CX you have to take on; choosing routes you by what you want to do.