RFC 0001 proposes a normative, implementation-independent Clojure language spec (the reader, evaluation model, special forms, data types, seq/laziness contracts, namespaces/vars, and the portable clojure.core surface) to the standard of R7RS/Racket — Clojure has none, and every alternative implementation re-derives semantics from the reference and folklore. The spec is executable-first: every numbered normative statement cites its conformance test or is marked UNVERIFIED. docs/spec/ carries the front matter (conformance terms, entry format, host classification), the special-form catalog with worked normative entries for if and let*, the core-library entry format with worked entries for first, reduce, and parse-uuid, and a generated coverage dashboard over the 694-var ClojureDocs inventory (tools/spec_coverage.py cross-references the surface against jolt's interned+resolvable vars and the test suites). Measured baseline: 380 implemented+tested, 154 implemented-untested, 35 portable-but-missing (filed), 22 resolvable-but-not-interned (filed — seed fns invisible to resolve/ns-publics), rest classified host/JVM/concurrency.
5.5 KiB
§3 Special Forms
Status: catalog complete; normative exemplars for if and let*; the
remaining entries follow the same format (tracked in coverage.md).
A special form is a form whose head symbol is evaluated by rule rather than by function application or macroexpansion. The special forms of Clojure are:
def·if·do·let*·fn*·loop*·recur·quote·var·throw·try/catch/finally·set!·monitor-enter·monitor-exit(host) · the interop forms.andnew(host)
let, fn, loop, and, or, when, … are macros over these (§8);
implementations MUST treat them as redefinable macros, not additional special
forms. monitor-enter/monitor-exit, . and new are host forms: their
syntax is specified here, their behavior is host-defined.
Special-form head symbols are not shadowable: a binding named if does not
change the meaning of (if ...) in operator position. ⚠ This matches the
reference; it differs from Scheme.
if — since 1.0
(if test then)
(if test then else)
Semantics
- S1.
testMUST be evaluated first, exactly once. - S2. Every value other than
nilandfalseis logically true. If the value oftestis logically true,thenMUST be evaluated and its value returned; otherwiseelse(ornilwhen absent) MUST be evaluated and its value returned. - S3. The branch not taken MUST NOT be evaluated.
- S4.
ifMUST be usable in tail position with respect torecur(§3recur): anifwhose branch is arecurform is a valid recur target path.
Edge cases
- E1.
(if test then)with a logically falsetestevaluates tonil. - E2. The empty collections (
(),[],{},#{}), the number0, and the empty string""are logically true (onlynil/falseare false). ⚠ This differs from several Lisps and is a frequent divergence source in alternative implementations.
Errors
- X1.
(if)and(if test)with fewer than two argument forms, or more than three, MUST be a compile-time error.
Examples
(if 0 :t :f) ;=> :t
(if "" :t :f) ;=> :t
(if nil :t :f) ;=> :f
(if false :t) ;=> nil
Conformance
S1–S3, E1–E2 → jolt forms-spec "if/do/def" group; truthiness group in
truthiness-spec; clojure-test-suite core_test/if.cljc. S4 → forms-spec
fn/loop recur cases. X1 → UNVERIFIED (arity-error case to add).
let* — since 1.0
(let* [sym₁ init₁ … symₙ initₙ] body…)
let* is the primitive sequential-binding form. The user-facing let macro
adds destructuring and expands to let* (§8); let* itself accepts only
simple symbols in binding positions.
Semantics
- S1. Each
initᵢMUST be evaluated in order, exactly once, in an environment wheresym₁…symᵢ₋₁are bound to their values (sequential scope, as Schemelet*). - S2. The body forms MUST be evaluated in order with all bindings in scope;
the value of the last body form is the value of the
let*form. An empty body evaluates tonil. - S3. A later binding MAY rebind the same symbol; each binding creates a new lexical binding visible from the next init onward (no mutation of the earlier binding is implied).
- S4. Bindings are lexical and immutable: there is no form that assigns to a
let*-bound local. (Closures capture bindings by value; see §3fn*.) - S5. The binding vector MUST be a vector literal with an even number of forms.
Edge cases
- E1.
(let* [] body)is valid and equivalent to(do body…). - E2. Binding a symbol that names a var shadows the var for the lexical
extent of the body;
(var sym)within that extent still denotes the var.
Errors
- X1. An odd number of binding forms MUST be a compile-time error.
- X2. A non-symbol in a binding position (e.g. a destructuring pattern) MUST
be a compile-time error for
let*— destructuring belongs to theletmacro. ("Bad binding form, expected symbol" in the reference.)
Examples
(let* [a 1 b (+ a 1)] (* a b)) ;=> 2
(let* [x 1 x (inc x)] x) ;=> 2
(let* [] 42) ;=> 42
Conformance
S1–S3, E1 → jolt forms-spec let group; clojure-test-suite
core_test/let.cljc; jank corpus form/let/*. X2 → jolt
destructuring-spec "primitives reject patterns". S4, X1 → UNVERIFIED
(cases to add).
Remaining entries (format above; status in coverage.md)
| Form | Notes for the entry author |
|---|---|
def |
var creation vs re-binding; metadata on the name; (def x) unbound; return value is the var |
do |
empty (do) → nil; top-level do splices for compilation units (important and under-documented) |
fn* |
arities, variadic &, closure capture, self-name, simple-symbol params only, recur target |
loop* |
recur arity must match bindings; recur rebinds in place |
recur |
tail-position rule (normative definition of tail position needed), across if/do/let*/try interactions |
quote |
self-evaluation table: which literals are self-evaluating unquoted |
var |
#' reader sugar; resolution at compile time |
throw |
any value vs Throwable — host question; jolt/cljs allow data, reference requires Throwable → classification needed |
try/catch/finally |
catch dispatch order, :default-style catch-all is a dialect extension (⚠ divergence note), finally evaluation guarantees, value of try |
set! |
host-dependent (dynamic vars + host fields) |
. / new |
syntax only; behavior host-defined |