journal: pin human-readable prose surface (family 20)

This commit is contained in:
2026-05-08 17:57:52 +02:00
parent 41804a39fb
commit d8e80cb9c5
+138
View File
@@ -8741,3 +8741,141 @@ build + test green.
common enough to warrant the schema field.
- Precise sub-binder analysis for the lint: waits for evidence
that the conservative cut misses real cases.
## 2026-05-08 — Pinned: human-readable prose surface (Family 20 candidate)
User feature-request, pinned for autonomous design pass:
> *"AILang ist für Menschen tatsächlich ziemlich unleserlich. Ich
> hätte gern einen Menschen-lesbaren Text-Output, der semantisch
> exakt identisch zu ail ist, aber von Menschen gut gelesen werden
> kann. Dabei sind Formatierung, Inlining, klare Identifier (keine
> Klammerhölle!), vllt. Infix-Notation, etc. wichtig. Und eventuell
> das Weglassen von Spezifika, die nur der Sprachstringenz dienen.
> Der Zweck ist: 1. Menschen sollen den Code schneller begreifen.
> 2. Sie sollen den Code durch FREITEXT editieren können. Der
> editierte Code und der Original-Code gehen dann wieder an das
> LLM, das dann in AIL die Freitext-Ergänzungen einbaut. Damit
> beginnt der Zyklus von vorne."*
### Read of the request
Two artefacts are wanted:
1. **A renderer.** `Module → human-prose String`. Indented,
infix-flavoured, parens dropped where unambiguous, Lispy
`(con T)` / `(term-ctor ...)` machinery suppressed in favour
of conventional notation (`T`, `Cons(1, Nil)`).
2. **A round-trip mediator.** Original .ail.json + edited prose
→ updated .ail.json. The mediator is the LLM, not the
compiler. Renderer is deterministic; round-trip is not (LLM
interprets free-text intent).
The user's word "semantisch exakt identisch" applies to the
**renderer's output as a projection of the source**: re-reading
the prose alongside the .ail.json gives a human all the semantic
information needed to reason about the program. The user's
"Weglassen von Spezifika, die nur der Sprachstringenz dienen"
explicitly authorises *lossy* projection — the prose may omit
machinery the LLM can re-derive (e.g. `consume_count` is already
inferred, so the prose definitely doesn't show it; redundant
parens can go; `(con Int)` becomes `Int`). The full .ail.json
remains the canonical source.
### What MUST stay visible in prose
Anything semantically load-bearing that the LLM cannot trivially
re-derive from prose alone:
- `(own T)` / `(borrow T)` mode annotations on fn signatures —
these are contracts (Decision 10), not buchhaltung. Render as
`own T` / `borrow T` or a sigil (`&T` / `~T`?). To be decided.
- Effect annotations (`IO`, `Diverge`) on user fns.
- Explicit `clone` calls — they're an author commitment, not a
free choice.
- Doc strings.
### What MAY go
- Outer `(module …)` wrap (filename or top-line is enough).
- `(con T)` wrapping around base types — render as `T`.
- `(term-ctor T C f1 f2)` — render as `C(f1, f2)` (or just
`Nil` for nullary).
- Redundant parentheses around expressions whose precedence is
unambiguous.
- Fully explicit `(fn-type (params ...) (ret ...))`
render as `(p1: T1, p2: T2) -> R`.
- `(let x v body)` — render as `let x = v` (newline) `body` or
`x := v; body`.
- Non-essential schema-rigour fields (e.g. `consume_count` if
the AST ever exposed it; today it's already inferred so this
is a forward-proofing note).
- The implicit `(do io/print_int x)` ceremony when `print` would
do.
### Round-trip cycle
For the editor cycle:
```
.ail.json ──[render]──→ .prose.txt
↓ user edits freely
.prose.txt' (edited)
.ail.json + .prose.txt' ──[LLM mediator]──→ .ail.json'
```
The LLM mediator is the contract-enforcer here: it integrates
free-text edits while preserving annotations the prose may have
omitted (e.g. carries forward the original `(own T)` annotation
unless the user's edit obviously contradicts it). This is *not*
a compiler pass — the renderer ships first, the mediator is
a separate piece (probably a `prompt_template` + a calling
convention, not a Rust crate).
### Iter scope
Probably a small family. First cut design:
- **Iter 20a — renderer skeleton.** New crate `ailang-prose`
with `pub fn module_to_prose(&Module) -> String`. Covers the
full AST with a conservative formatting policy (no infix yet,
no inlining, just kill the `(con ...)` / `(term-ctor ...)`
noise and use proper indentation + commas). One snapshot test
per fixture in `examples/`.
- **Iter 20b — formatting polish.** Infix for arithmetic
(`(i64-add a b)``a + b`), drop redundant parens, inline
short let-bindings, prettier match arms. Snapshot tests
update; behavioural surface unchanged.
- **Iter 20c — `ail prose` CLI subcommand.** `ail prose <file.ail.json>`
prints the prose to stdout. Symmetric to `ail parse`'s role.
- **Iter 20d — round-trip ergonomics.** This is where the LLM
mediator's input format gets specified. May be a documented
prompt template under `docs/`, may grow into a `ail merge-prose`
subcommand that wraps an LLM call. Defer until 20ac are real
code we're using.
### Why this is its own family
Decision 10 / Family 18 was the memory model. Family 20 is a
**presentation layer** — it touches no semantics, no codegen, no
typechecker. Lives entirely above `ailang-core` as a sibling of
`ailang-surface` (which is the form-A *parser* / printer; prose
is the form-B *projection*).
### What's deferred / open questions for design pass
- How to render `(own T)` / `(borrow T)` in prose. Sigil vs.
keyword. Sigils are denser; keywords align with the
explicit-intent philosophy. Lean one way and document
rationale.
- Module headers / imports — render or suppress?
- Whether prose carries hash/version annotations for the
round-trip (so the mediator can detect drift between prose
and the .ail.json the user *thinks* they're editing).
- Whether snapshot tests live in `crates/ailang-prose/tests/`
or `examples/` (as `.prose.txt` siblings to the .ail.json).