Skip to content

Language reference: a beginner's book that quotes the sources - #69

Open
Russoul wants to merge 6 commits into
mainfrom
language-reference
Open

Language reference: a beginner's book that quotes the sources#69
Russoul wants to merge 6 commits into
mainfrom
language-reference

Conversation

@Russoul

@Russoul Russoul commented Aug 27, 2026

Copy link
Copy Markdown
Owner

A third rendered page alongside the theory specs and the syntax-highlighted sources: a tutorial and reference for the surface language. It assumes a functional programmer who reads mathematics, and assumes nothing about dependent types or proof assistants. Unlike docs/*.txt it is not normative — it teaches the language and links into the specs.

46 chapters in nine parts; 18 written, 28 outlines. The page reports that itself, in a progress line, and badges every unfinished chapter — so it is honest about its own state rather than looking finished and being hollow.

What is here

  • docs/reference/*.md — the book. Parts I–III are complete (Orientation, Writing Nova, Data and recursion), plus The discharge engine and Types without coherence from later parts.
  • tools/render-reference.py — renders it to one self-contained page, reusing render-specs.py's palette and the LSP's own token classes, so a snippet reads exactly like the same line on the sources page.
  • tests/nova/docs/ — nine goldens: the quotation check, and eight fixtures pinning the worked examples.
  • Flake wiringnix/packages.nix renders reference.html into .#site, and --check is a flake check of its own.

Structure

Part
I Orientation ✅ what this is, what a checker is for, installing, your first file
II Writing Nova ✅ notation, definitions, let, operators, modules
III Data and recursion ✅ base types, pairs, sums, your own types, eliminators, clauses
IV Types that depend on values the idea, Π, Σ, universes, implicits, indexed families, checking vs inference
V Propositions and proofs propositions, equality, reflection, induction, chains, connectives, quotients
VI Working with the checker the report, holes and in-place elimination, the discharge engine, using, recipes, pitfalls
VII Coming from Agda a map, reflection instead of J, types without coherence
VIII Beyond the basics QIITs, coinduction
IX Reference grammar, precedence, generated names, library, tooling, glossary

Three decisions worth a reviewer's attention. Recursion is introduced as a surprise — "there is no general recursion, and the name of a definition is not in scope in its own body" is the first thing that ambushes a functional programmer, so eliminators arrive as the answer rather than as a primitive. The Agda comparison is late and optional, because a reader who has never used a proof assistant has no comparison point; chapter 1 points those who do straight at Part VII. And chapter 4 fails twice on purpose before the file is accepted, because the obligation loop is what no summary conveys — one failure carries a hint: (a licence is missing), the other does not (the mathematics needs an induction), which hands a beginner a triage rule from their first obligation.

The book quotes the sources; the suite enforces it

The reference does not copy the corpus, it quotes it, and render-reference.py --check verifies every quotation:

Fence / citation Must occur verbatim in
```nova src/nova/*.nova or a golden's input.nova
```report a golden's expected
docs/*.txt, src/nova/*.nova, tools/* paths the tree
backticked rule names (el-reflect, prop-lift, …) the rulemap render-specs.py builds from the specs

Matching is by blank-line-separated chunk: a snippet may elide whole items, but each chunk must appear as a contiguous, dedented, verbatim run of lines. Golden inputs count as a source because a deliberately failing example cannot live in the accepted corpus. ```nova-sketch is the escape hatch for genuinely illustrative code — grammar skeletons and error cases — highlighted, exempt, and visible as an exemption in the source. Currently 98 quoted snippets, 17 illustrative.

Chapter 5 is built from a census of the corpus — Nova code uses exactly 32 non-ASCII characters, twenty of them the language — and a check verifies that every one still appears in the chapter.

It has already caught real drift, twice

The Prf retirement. The gate flagged two drifted snippets; reading the commits turned up four further stale claims in prose that no gate could catch, including a data signature using a retired spelling and advice to write El ℕ for external domains that had become exactly backwards.

The last thirty commits. The gate caught eight transcripts and two snippets. Reading the commits caught the rest: became × and is now unrecognised, so chapter 5's look-alike table had the advice inverted; × moved below , which deleted the precedence trap chapter 11 was built around; ASCII fallbacks mean chapter 5 no longer opens by telling a newcomer they must type Unicode; ?x holes returned, contradicting chapter 4 outright; and VS Code and neovim clients now ship from the flake.

That is the argument for the gate: mechanical drift is caught automatically, and the prose it cannot check is at least localised for a human to re-read.

Outline audit

Checked every production of the surface grammar, every CLI command and every section of NovaElaboration.txt against the chapter set. Three user-visible features had no home, all from the recent batch:

  • In-place eliminationnova eliminate <file> <line>:<col> <var> fills a hole by eliminating a variable of its context and prints the file back, turning λn. ?goal into λn. (ℕ-elim ?goalZ (n ih. ?goalS) n).
  • Refinement — synthetic holes are solved from the run's own constraints, so a goal reads as ?p : x ≡ y ∈ A rather than as elaborator scaffolding. Holes you wrote are never guessed at.
  • Recovery — a failed item no longer hides every goal after it.

Part VI now has Reading the report and a new Holes and in-place elimination; the tooling outline lists every command and gate script.

Other things worth knowing

  • Chapter order lives in PARTS in the script and files are named by slug, so inserting a chapter renumbers nothing.
  • Cross-references are anchors, never chapter numbers, with a build-time dead-anchor guard — it caught seven broken links during the restructure and four more when a chapter was split.
  • The snippets golden's expected is a hand-written success line; its run script says why, after regenerating it from a red run once baked a failure in.
  • Code blocks bleed right of the prose column above 1200px, because real corpus lines are wider than a comfortable measure; the page body never scrolls sideways.
  • Equality is written but still pitched at the old audience; it carries a %revise badge rather than pretending otherwise.

195/195 tests, six flake gates.

@Russoul
Russoul force-pushed the language-reference branch from a32e556 to 472c80c Compare August 27, 2026 19:40
@Russoul Russoul changed the title Language reference: a human-facing book that quotes the sources Language reference: a beginner's book that quotes the sources Aug 27, 2026
A third rendered page alongside the specs and the sources: a tutorial
and reference for the SURFACE language, 36 chapters across seven parts,
four written and the rest outlines (badged as such, counted in the
page's progress line).

tools/render-reference.py renders docs/reference/*.md — a small fixed
Markdown subset — to one self-contained page, reusing render-specs.py's
palette and the LSP's own token classes so a snippet reads exactly like
the same line on the sources page. Chapter ORDER lives in PARTS, files
are named by slug, and cross-references are anchors, so inserting a
chapter renumbers nothing and can rot no link.

The book QUOTES the corpus rather than copying it, and --check enforces
that: every ```nova block must occur verbatim in src/nova or a golden's
input, every ```report block in a golden's expected output, every cited
path must exist, and every rule-shaped citation must name a rule the
specs still define. A golden test (tests/nova/docs) and a CI step run
it, so the book fails the suite when the language moves under it.
The audience changes from "you have used a proof assistant" to "you
have written functional programs and read mathematics, and have met
neither dependent types nor a proof assistant". The spine is rebuilt
around that reader: 45 chapters in nine parts, teaching the language
before the theory, with the Agda comparison moved out of the opening
and into its own late, optional part (a map, reflection-vs-J, and the
coherence chapter that was always an Agda-facing piece).

Five chapters are written for the new reader: Introduction, Proofs and
programs (propositions-as-types from scratch), Installing and running,
Your first file, and Reading and typing Nova. Equality still carries
the old pitch and is badged %revise — a new status marker beside
%stub, so the page reports what is finished, what is planned, and what
is stale.

Chapter 4 walks a file that fails twice before it is accepted, because
the obligation loop is the thing no summary conveys. Four goldens
under tests/nova/docs pin its file and its three transcripts, so a
change to the checker's output breaks the chapter rather than
stranding a reader mid-walkthrough. Chapter 5 is built from a census
of the corpus: Nova code uses exactly 32 non-ASCII characters, twenty
of them the language, and a --check pass now verifies that every one
of them appears in the chapter.
main moved the gates and the Pages artifact into the flake, which
silently dropped the reference from both: nix/packages.nix's site
rendered specs and sources but not reference.html, so the landing
page's first card would have 404'd on the next deploy. The render is
back in `site`, and `--check` is a flake check of its own — it needs
no Idris build, so it reports in seconds, alongside spec-rules.

Chapter 3 now opens with two routes instead of one. Nix leads: `nix
run . -- elab file.nova` cannot fail halfway through bootstrapping a
compiler, which is the failure mode a newcomer is least equipped to
diagnose. pack follows, for readers already developing in Idris. The
book's commands become toolchain-neutral `nova elab file.nova`, with
one callout giving the four spellings, and `nix flake check` joins
./test.sh as the way to check your work.
Definitions, let, operators and modules — the mechanical chapters a
reader needs before Part III can teach anything. Each claim was run
against the checker rather than taken from the spec, which is where
the content came from:

* a `type` abbreviation is opaque like any other definition, and the
  error names the remedy ("head exposure blocked for Pair — cite
  Pair.unfold");
* an operator with no fixity used infix is not a parse error, it is
  three atoms juxtaposed and a baffling type error;
* operators overload across modules and resolve by type, provided the
  overloads agree on fixity — the loader says so if they do not;
* imports are qualified by default and NOT transitive for visibility,
  while the lemma store does travel the closure;
* import cycles are refused by the loader before any checking.

The precedence table is the corpus's own, and a scripted cross-check
confirms every fixity the corpus declares appears in it.
The built-in types, pairs, sums, defining your own types, recursion
and defining equations. Part III is where a functional programmer
meets the two things that do not transfer, so both are given a chapter
of their own rather than a warning in passing: `𝟘`/`𝟙` earn their
place by computing types (isZero), and recursion opens with the fact
that a definition's name is not in scope in its own body.

Findings from running everything, which shaped the chapters:

* `⨯` and `→` share a precedence level and associate right, so
  `ℕ ⨯ 𝟙 → 𝟙 ⨯ ℕ` is a PAIR type and a λ against it fails oddly. It
  is now a named trap in the pairs chapter.
* η for pairs and functions is licensed, not automatic: without
  `sigma.eta` / `pi.eta` you get an obligation reading
  `p .π₁, p .π₂ ≐ p`, which is baffling until you know it wants a
  licence rather than a proof.
* Clauses outside the structural fragment are not rejected — the
  macro declares the CONTRACT instead (the function, each clause as an
  equation, and uniqueness) and you owe the three. That behaviour is
  the best explanation of what the clause form is for, so the chapter
  is built around it.

Four new fixtures under tests/nova/docs pin what the corpus cannot
supply: a library has no standalone demonstration of `()` or of a
first user-defined List. 166/166 tests.
… missing

Thirty commits landed upstream, several of which contradicted the
book. The quotation gate caught the mechanical drift (eight
transcripts, two snippets); reading the commits caught the rest:

* ⨯ (U+2A2F) became × (U+00D7) and ⨯ is now unrecognised, so chapter
  5's look-alike table had the advice exactly backwards.
* × split in two and moved below ⊎, which DELETED the precedence trap
  chapter 11 was built around: `A × B → C` now reads the way everyone
  expects. Replaced with the real table, and the binder form's
  maximal body, which is the remaining surprise.
* Every non-ASCII token gained an ASCII spelling, so chapter 5 no
  longer opens by telling a newcomer they must type Unicode.
* Holes came back. `?x` is inert, reported with its telescope, and
  hoverable — chapter 4 said flatly that no such syntax existed.
* VS Code and neovim clients ship from the flake; chapter 3 said there
  were none.
* Mixed associativity at equal precedence is now a parse error.
* `at:` lines carry file:line:col, and parse errors render a caret.

Outline audit against the grammar, the CLI and the spec's sections
found three user-visible features with no home, all from this batch:
refinement, item recovery, and in-place elimination — `nova eliminate`
case-splits a hole and prints the file back, which is the other half
of hole-driven development. Holes are now their own chapter beside a
narrowed Reading the report, and the tooling outline lists every
command and gate script. 46 chapters.

One self-inflicted bug fixed: regenerating every fixture at once had
overwritten the snippets golden while the check was red, baking its
own failure in as `expected`. Its run script now says why that file is
written by hand.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant