the language tour

The shape of curt.

Whole program = top-level statements, run in order. No imports, no main, no significant indentation. Inference is total — you annotate nothing. Here is the whole language in six blocks.

Heads up — this page is for people who write code; it shows how curt actually looks and works. If you just want to know what curt is and why it exists, the home page is the friendlier start.

toolchainparse · check · fmt · expand · tokens · dense · run

core shape

greet name = "hi {name}"        # equation: name params = expr
total = [1,2,3].sum             # binding (= rebinds; x += 1 works)
(a, b) = (1, 2)                 # tuple destructuring
print greet "ana"               # application is juxtaposition: f x y
An equation is a function: name params = expr. No def, no return (use ret to exit early).
Strings interpolate: "{x} and {y.len}". '…' is a raw string.
Application is juxtaposition — f x y, no parens, no commas.

pipes & projections

xs | keep .active | top 2 .score | map .name
xs.keep(f).map(g)               # glued dots chain; spaced → use |
ws | join "-"                   # dense verbs over loops
Pipeline | feeds the value as the LAST argument of the next stage.
Bare .field is a lambda: top 2 .scoretop 2 (x -> x.score).
UFCS: x.f af x a — so xs.len, s.upper, xs.map g.

types & untagged unions

type Pt = {x float, y float}
show v = match v { float x -> "num {x}", str s -> "sym {s}" }
nums = [7, "ok"]                # a [int | str] union list
Inference is total — annotate nothing (except pub/FFI). Primitives int float str bool bytes.
Unions are untagged: int | str, T | err — the measured answer to the ADT tax (no wrapper tokens).
match narrows by runtime type and is exhaustiveness-checked over union members.

errors — two one-token tools

cfg = fs.read "app.cfg" ? ""    # rescue: fallback if the read errs
data = (fs.read p)?             # propagate: return err to caller
ok = match v { err e -> "failed: {e}", int n -> "got {n}" }
Failable ops return T | err. Spaced a ? b rescues; glued x? propagates.
fs/net are capability-gated, deny-by-default — ungranted, they yield err, never crash.
Diagnostics are single-line JSON with a verbatim fix field — apply it as-is.

dense beats loops (pairs execution-verified identical)

best = xs[0]                              # 22 tokens
for x in xs { if x > best { best = x } }
best = xs.max                             # 4 tokens

print (ws | join "-")                     # 7 vs 27 tokens
Verbs first: xs.max is 4 tokens where the explicit loop is 22 — and the densifier verifies they compute the same thing.measured o200k · curt dense
Loop only when state threads between iterations; otherwise reach for a verb.