CX · Reference guideWhy CX
PlaygroundDownloadsAboutv0.18.0-pre.1

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.