Three canonical-conformance fixes from the post-shrink batch: - bit-and/bit-or/bit-xor/bit-and-not get Clojure's variadic arities as 20-coll shells folding the binary host ops (now __bit-* seams). 2-arg call sites still compile to the native janet op via the backend's native-ops table. The passes.clj constant-fold table now names the seams — the public fns are overlay and don't exist when the compiler loads (this briefly broke every compile-mode init). - core-set? recognizes the :jolt/sorted-set representation (jolt-dpn): (set? (sorted-set 1)) was false, and ifn? on sorted sets inherited the bug. - (if) / (if test) / (if test then else extra) throw in both the analyzer and the interpreter — spec 03-special-forms X1, now marked verified. Suite 4704 -> 4706; bench and the greeter example benchmark are flat.
5.6 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 → forms-spec "if arity (X1)" (0/1/4-arg forms throw
in both the analyzer and the interpreter).
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 |