docs: language specification RFC + spec skeleton with normative exemplars

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.
This commit is contained in:
Yogthos 2026-06-10 10:53:44 -04:00
parent bd5142abac
commit 7003926eda
7 changed files with 1478 additions and 0 deletions

View file

@ -0,0 +1,179 @@
# RFC 0001 — A Specification for the Clojure Language
- **Status**: Draft
- **Champions**: jolt maintainers
- **Created**: 2026-06-10
## Summary
Produce a normative, implementation-independent specification of the Clojure
language — the reader, the evaluation model, the special forms, the data types
and their equality/hashing/ordering contracts, sequences and laziness, and the
`clojure.core` library — to the standard set by R7RS Scheme and the Racket
reference. The specification is developed *in this repository*, validated
continuously by jolt's executable conformance suite, and intended to be useful
to every alternative implementation (ClojureScript, jank, babashka/sci,
Basilisp, ClojureCLR, jolt).
## Motivation
Clojure has no specification. The language is defined by:
1. the reference JVM implementation's source,
2. docstrings (frequently silent on edge cases),
3. community folklore (ClojureDocs examples, mailing-list threads),
4. each alternative implementation's reverse-engineering effort.
Every alternative implementation independently re-derives answers to the same
questions — *what does `(nth coll nil)` do? is `(first "")` an error? does
`conj` on `nil` produce a list or vector? in what order does `reduce-kv` visit
a map?* — and they routinely diverge. The cross-dialect
[clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) exists
precisely because these divergences are real and frequent: it currently
encodes hundreds of edge-case assertions that no normative document captures.
Building jolt's self-hosted compiler forced us to answer these questions
one at a time (the conformance harness runs every behavior through three
independent execution paths and demands agreement). That work product — over
300 three-way-validated conformance assertions, ~1,500 behavioral spec cases,
and a frozen catalog of which forms are language vs. host — is the seed of a
specification, currently trapped in test files. This RFC proposes promoting it
into prose with normative force.
### Why us / why now
A useful spec needs an implementation that can *afford* to be strict. The
reference implementation can't adopt a spec retroactively without breaking
changes; an alternative implementation chasing drop-in compatibility can't
deviate from the reference even where the reference is accidental. jolt's
goals (self-hosted, minimal seed, multiple execution paths that must agree)
already require us to decide, for every form, *what the contract is* — we are
writing the spec anyway, in test form. The marginal cost of writing it down
properly is small; the value to the ecosystem is large.
## Goals
1. **Normative core**: reader grammar, evaluation model, all special forms,
data types with equality/hashing/ordering contracts, seq/laziness
contracts, namespaces/vars, and per-var entries for the portable
`clojure.core` surface.
2. **Executable**: every normative statement is paired with at least one
conformance test. The spec and the suite are maintained together; a spec
claim without a test is marked `unverified`.
3. **Host classification**: every `clojure.core` var is classified
**portable** (specified normatively), **host-dependent** (interface
specified, behavior host-defined — e.g. `slurp`, `*out*`), or
**JVM-specific** (documented as outside the portable language — e.g.
`bases`, `definline`, agents/STM as currently scoped).
4. **Versioned against reference Clojure**: each spec edition states the
reference version it describes (initially 1.12) and records *deliberate*
divergences (e.g. where reference behavior is accidental — these become
labeled "implementation-defined" with the reference behavior noted).
5. **Useful to other implementations**: no jolt-specific concepts in
normative text. jolt appears only in conformance-suite references.
## Non-goals
- Specifying the JVM interop surface (`proxy`, `gen-class`, `.`-forms beyond
their syntax), agents, STM refs, or the Java class hierarchy mapping.
These are catalogued as host/JVM surface, not specified.
- Specifying `clojure.spec`, `core.async`, or other contrib libraries
(candidates for later, separate documents).
- Changing the language. The spec describes Clojure as it is; divergence
decisions document reality, they don't invent semantics.
- Replacing clojure-test-suite — we contribute to it and cite it.
## The specification document
Lives in `docs/spec/`. Shape (mirroring R7RS chapters):
| § | Document | Content |
|---|---|---|
| 0 | `00-front-matter.md` | conformance terms (RFC 2119), entry format, host classification |
| 1 | `01-evaluation.md` | evaluation model: forms, environments, vars, macroexpansion order |
| 2 | `02-reader.md` | lexical syntax: formal grammar, all reader macros, reader conditionals |
| 3 | `03-special-forms.md` | the special forms, one normative entry each |
| 4 | `04-data-types.md` | nil/booleans/numbers/strings/chars/keywords/symbols/colls; equality, hashing, ordering |
| 5 | `05-sequences.md` | the seq abstraction, laziness contract, realization boundaries |
| 6 | `06-namespaces-vars.md` | namespaces, vars, dynamic binding, resolution |
| 7 | `07-polymorphism.md` | protocols, records/types, multimethods, hierarchies |
| 8 | `08-macros.md` | defmacro, syntax-quote/hygiene, `&env`/`&form` |
| 9 | `09-core-library.md` | normative per-var entries for the portable surface |
| A | `coverage.md` | generated status dashboard: 694 vars × {specified, tested, implemented, classification} |
### The normative entry format
Every special form and library var gets an entry with these fields
(exemplars in `03-special-forms.md` and `09-core-library.md`):
```
### name
Signature(s), since-version
1. Semantics — numbered MUST/SHOULD statements
2. Edge cases — nil, empty, bounds, wrong-type behavior (normative)
3. Errors — what MUST throw, and when error type is implementation-defined
4. Examples — executable, drawn from ClojureDocs where community-validated
5. Conformance — test IDs that verify each numbered statement
```
### Evidence sources, in priority order
1. **Differential testing** against reference Clojure 1.12 (the ground truth
for behavior questions).
2. **clojure-test-suite** (cross-dialect agreement = portable semantics;
dialect splits = host-dependent candidates).
3. **ClojureDocs export** (`clojuredocs-export.edn`, 694 core vars, 648 with
community examples) — examples become spec examples after verification.
4. **jank's language test corpus** (~800 per-form tests under
`test/jank/{form,call,metadata,reader-macro,syntax-quote,var}`) — the
per-construct granularity model for §2§3 conformance.
5. Reference implementation source — last resort, for intent.
## Current baseline (measured 2026-06-10)
- ClojureDocs inventory: **694** `clojure.core` vars (648 with examples).
- jolt implements **572**; **373 (66%)** are exercised by the behavioral
spec/conformance suites; 139 implemented-but-untested.
- Initial classification of the 182 unimplemented: ~31 dynamic vars, ~20
agents/taps, ~11 STM, ~15 special-form docs, ~105 to adjudicate
(genuinely-portable gaps spotted already: `compare`, `any?`, `update-keys`,
`update-vals`, `parse-long`, `parse-double`, `parse-boolean`,
`partitionv`, `splitv-at`, `macroexpand`, `time`, `with-redefs`).
- Conformance: 302 assertions × 3 execution paths; ~1,500 behavioral cases;
clojure-test-suite ≥ 4081/4707 assertions.
## Process
1. **Section by section**, in chapter order. §2 (reader) and §3 (special
forms) first — they are the smallest closed sets and jank's corpus gives
per-construct conformance shape immediately.
2. Each PR that adds/edits normative text MUST add or cite the conformance
tests for every numbered statement, and update `coverage.md`.
3. Divergences from reference Clojure discovered during writing get filed,
then either fixed in jolt or recorded as a labeled divergence — never
silently spec'd to jolt's behavior.
4. Editions: spec snapshots versioned independently of jolt releases
(`Clojure Language Specification, Draft N`).
5. When a chapter stabilizes, solicit review from other implementations
(jank, babashka, Basilisp maintainers) before marking it Stable.
## Alternatives considered
- **Contribute prose to clojure-test-suite instead**: the suite is the right
*conformance* home but tests can't express rationale, classification, or
grammar; both are needed and they cross-reference.
- **Spec only what jolt implements**: rejected — the host classification of
the *full* 694-var surface is half the value.
- **EDN/data-format spec only** (edn already has a loose spec): far too
narrow; the evaluation model and core library are where divergence lives.
## Open questions
1. Numerics: the reference has longs/doubles/ratios/BigInt with promotion
rules; CLJS has JS numbers; jolt has Janet numbers. Likely answer: specify
an integer/float core with a host-numeric-tower extension point — needs
its own design note in §4.
2. Where do `*print-length*`-style dynamic vars land — host-dependent
interface or portable with defaults?
3. License/venue if the spec outgrows this repo (likely CC-BY; separate repo
once §1§3 stabilize).