Skip to content

let-expressions in the element language - #48

Merged
Russoul merged 3 commits into
mainfrom
let-expression
Aug 4, 2026
Merged

let-expressions in the element language#48
Russoul merged 3 commits into
mainfrom
let-expression

Conversation

@Russoul

@Russoul Russoul commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Adds let-expressions to Nova, end to end: theory, kernel and elaboration specs, implementation, tests, and a corpus file.

Closes #47

Theory (NovaFoundation.txt)

The strong (definition-carrying) form: in let a b, the body is typed under two binders — the definiens' value and its unfolding equation Prf (☐₀ ≡ a[↑] ∈ A[↑]) — so el-reflect makes the definiendum unfold judgementally inside the body. This is a definitional context entry with no new context former: extensionally, a definition is a variable plus a Prf of its unfolding. Conservative (let a b ≐ ((λ (λ b)) a) ⋆); retained so a local definition reads and prints as one rather than as its λ-plumbing. Rules: el-let, el-let-beta (let a b ≜ b[id, a, ⋆]), el-sub-let, el-let-cong. No eta, no injectivity — it is not a type former.

Kernel spec & implementation

A let is always a redex, so nf contracts every one and normal forms contain no let — equation replay, selectors, and the typed descent needed no extension. Additions: the nf contraction clause, child indexing (let a b → 0, 1(2)), and item-level bidirectional rules — definiens inferred, body checked at T[↑ ∘ ↑] under the value and its unfolding equation (fully general, not an approximation: under the hypothesis (id, a, ⋆) ∘ (↑ ∘ ↑) ≐ id). The proof fragment excludes let.

Elaboration spec & implementation

Surface form let x ≔ e in b, with let x : T ≔ e in b as parse-level sugar for an ascribed definiens. let is both-mode: the body elaborates in the ambient mode (e-let / e-let-check), so checking-only body forms need no ascription. The unfolding hypothesis is a Prf of an equality prop, so the equation store's existing HYPOTHESIS source reflects x ≐ e into discharge automatically — the definition is transparent inside the body with no new mechanism. The body's surface indices are counted against the core two-binder context (the parser pushes a never-resolvable wildcard slot for the hypothesis, so x resolves to index 1). The printer renders core lets back as let x ≔ e in b, the hypothesis entry anonymous:

[1] (n : ℕ) (m : ℕ) (_ : m ≡ S n ∈ ℕ) ⊢ m ≐ Z : ℕ

Tests & corpus

Five new goldens (kernel parse, surface parse, accepted file, obligation report, evaluation); src/nova/letExpr.nova exercises nested and annotated lets, a let in inference position, in-body transparency (a stated at the abbreviation paying a goal stated at the unfolding), and the outside view (a let is judgementally its unfolding). Suite: 132/132 tests, 26/26 corpus elaborations.

Russoul added 3 commits August 4, 2026 03:13
Strong (definition-carrying) form: the body is typed under the
definiens' value and its unfolding equation, so el-reflect makes the
definiendum unfold judgementally inside the body — a definitional
context entry with no new context former. Conservative:
let a b ≐ ((λ (λ b)) a) ⋆; retained so a local definition reads and
prints as one. Rules: el-let, el-let-beta, el-sub-let, el-let-cong.
Kernel: el-let-beta as an nf contraction (a let is always a redex, so
normal forms and equation replay never meet one — no new finals or
selectors), child indexing, item-level bidirectional rules (checking
at T[↑ ∘ ↑] is fully general under the unfolding hypothesis); the
proof fragment excludes let.

Elaboration: surface form 'let x ≔ e in b' (annotated variant sugar
for ascription), e-let / e-let-check propagating the ambient mode to
the body, definiens always inferred; the unfolding hypothesis feeds
E's existing HYPOTHESIS source, so the definition is transparent in
the body with no new discharge mechanism.
Core: Let former (Syntax/Subst/Beta/Compute — always a redex, one
fuel per contraction); kernel item-level bidirectional rules (body
checked at T[↑ ∘ ↑] under the value and its unfolding equation);
excluded from the proof fragment and the typed descent, per spec.

Elaboration: surface 'let x ≔ e in b' (annotated definiens sugar for
ascription), both-mode e-let/e-let-check; the body's surface indices
are counted against the core two-binder context (the parser pushes a
wildcard slot for the unfolding hypothesis, so x resolves to index 1);
the hypothesis feeds the existing HYPOTHESIS discharge source, making
the definition transparent inside the body. Printer renders the core
form back as 'let x ≔ e in b' with the hypothesis entry anonymous.

Tests: kernel + surface parser goldens, accepted-file and obligation-
report goldens, an evaluation golden; src/nova/letExpr.nova exercises
nested/annotated lets, inference position, and in-body transparency.
@Russoul
Russoul merged commit 63f5f61 into main Aug 4, 2026
3 checks passed
@Russoul
Russoul deleted the let-expression branch August 4, 2026 00:05
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Nova Progress tracker Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Let expression

1 participant