This document provides a sketch of the strong normalization technique used in the Per theorem prover, specifically focusing on the implementation of Girard's Reducibility Candidates in both the OCaml model and the Elixir production implementation.
Normalization in Per is based on Normalization by Evaluation (NbE). The core idea is to evaluate terms into a domain of "values" (semantic domain) and then "read back" (reify) those values into normal forms (syntax).
Girard's Reducibility Candidates (CR) technique is the standard method for proving strong normalization for systems like System F and MLTT. In Per, this is practically manifested through the handling of Neutral terms and Type-in-Type (Girard's Paradox toggle).
Strong normalization relies on the distinction between Canonical forms (constructors like Lam, Pair, Universe) and Neutral terms (stuck computations).
A term is neutral if it is a variable or a projection/application from a neutral term.
- OCaml:
Var,VApp,VFst,VSnd,VAppFormula, etc. - Elixir:
%AST.Neutral{term: term, type: type}
Neutral terms allow the evaluator to proceed even when the exact value is unknown (e.g., inside a binder during readback).
The normalization process is driven by an incremental reduce (or app) function that performs beta-reduction specifically when a redex is formed.
The eval function maps syntax to the semantic domain.
- OCaml:
Check.eval - Elixir:
Per.Typechecker.eval
The app function (and appFormula, vfst, vsnd) performs the incremental reduction:
- If the function is a
Lam, it applies the body (Beta-reduction). - If the function is
Neutral, it constructs a newNeutralterm (accumulating the "stuck" operation).
# lib/per/typechecker.ex
defp app(f, x) do
case f do
%AST.Lam{body: func} -> func.(x) # Beta-reduction
%AST.Neutral{term: term, type: %AST.Pi{codomain: b}} ->
%AST.Neutral{term: %AST.App{func: term, arg: x}, type: b.(x)}
_ -> %AST.App{func: f, arg: x}
end
endThe girard flag in Per explicitly allows Universe : Universe (Type-in-Type), which is known to lead to Girard's Paradox (non-termination).
- OCaml:
let ieq u v = !girard || u = v - Elixir:
Process.get(:per_girard, false) or u == v
When girard is false, the system enforces a strict universe hierarchy (
| Feature | OCaml (check.ml) |
Elixir (typechecker.ex) |
|---|---|---|
| Domain | Higher-order (functions) | Higher-order (functions) |
| Neutrality | Implicit in value type |
Explicit %AST.Neutral |
| Partiality | Full VSystem support |
Simplified Partial |
| Readback | rbV recursive function |
readback recursive function |
- Partial Terms: The Elixir implementation of
EPartialis currently a skeletal version of the OCamlVSystemlogic. In OCaml,EPartialcreates aVLamthat returns aVPartialPwrapping a system of faces. Elixir'seval/2for%AST.Partial{}simply returns the evaluated expression without the full face-solving logic found incheck.ml:112.
- Evaluation: Syntax -> Semantic Values (using environment).
- Incremental Reduction:
app(Lam f, x)executesf(x). - Neutral Accumulation: If
fis neutral,app(f, x)is neutral. - Readback: Reify semantic values back to syntax by applying neutral variables to binders.