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 .score ≡ top 2 (x -> x.score).UFCS:
x.f a ≡ f 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 denseLoop only when state threads between iterations; otherwise reach for a verb.