Module expressions, basics #
The raw calculus underlying modules: ModuleTypeRep (the types), the untyped syntax tree
ModuleExpression with extrinsic typing (ModuleExpression.HasType), de Bruijn
renaming/substitution with the σ-calculus laws they satisfy, call-by-value reduction with its
normal forms, and the deciding tactics moduletyping and normalmodule.
This is the part of the development that depends neither on confluence
(ModuleExpressionConfluence.lean) nor on normalization
(ModuleExpressionNormalization.lean); their consequences — the reduce theory and the
reduce_simp tactic — live in ModuleExpression.lean, which is what downstream modules import.
Modules.lean builds Module T — a closed, well-typed, normal expression — on top of that.
Possible types of modules.
- proc : ProcedureSignature → ModuleTypeRep
- prod : ModuleTypeRep → ModuleTypeRep → ModuleTypeRep
- arr : ModuleTypeRep → ModuleTypeRep → ModuleTypeRep
- unit : ModuleTypeRep
Instances For
Instances For
The right-nested product type of a hole signature (native UM copy of the TypedModules definition;
namespace-scoped under GaudisCrypt to avoid clashing with TypedModules's while both coexist).
Equations
Instances For
Untyped module expressions: the raw syntax tree of the module calculus, with no
context/type indices. Well-typedness is captured extrinsically by the predicate
ModuleExpression.HasType. Variables are de Bruijn indices (Nat); abs carries no
domain annotation (the domain is recovered by HasType).
- proc {sig : ProcedureSignature} : Procedure sig → ModuleExpression
- procHoles {holes : HoleSigs} {sig : ProcedureSignature} : holes.NonEmpty → ProcedureWithHoles holes sig → ModuleExpression
- var : ℕ → ModuleExpression
- app : ModuleExpression → ModuleExpression → ModuleExpression
- fst : ModuleExpression → ModuleExpression
- snd : ModuleExpression → ModuleExpression
- abs : ModuleExpression → ModuleExpression
- pair : ModuleExpression → ModuleExpression → ModuleExpression
- unit : ModuleExpression
Instances For
ModuleExpression.HasType m Δ T holds when the untyped expression m is well-typed in
module context Δ with type T. This is the extrinsic counterpart of the indices of
TypedModules.ModuleExpression: every TypedModules.ModuleExpression Δ T erases (via
TypedModules.ModuleExpression.erase) to an m with HasType m Δ T, and conversely.
- proc {Δ : ModuleContext} {sig : ProcedureSignature} (p : Procedure sig) : (ModuleExpression.proc p).HasType Δ (ModuleTypeRep.proc sig)
- procHoles {Δ : ModuleContext} {holes : HoleSigs} {sig : ProcedureSignature} (ne : holes.NonEmpty) (p : ProcedureWithHoles holes sig) : (ModuleExpression.procHoles ne p).HasType Δ (holes.toModuleTypeRepTuple.arr (ModuleTypeRep.proc sig))
- var {Δ : ModuleContext} (n : ℕ) (h : n < List.length Δ) : (ModuleExpression.var n).HasType Δ Δ[n]
- app {Δ : ModuleContext} {A B : ModuleTypeRep} {m n : ModuleExpression} : m.HasType Δ (A.arr B) → n.HasType Δ A → (m.app n).HasType Δ B
- fst {Δ : ModuleContext} {A B : ModuleTypeRep} {m : ModuleExpression} : m.HasType Δ (A.prod B) → m.fst.HasType Δ A
- snd {Δ : ModuleContext} {A B : ModuleTypeRep} {m : ModuleExpression} : m.HasType Δ (A.prod B) → m.snd.HasType Δ B
- abs {Δ : List ModuleTypeRep} {A B : ModuleTypeRep} {m : ModuleExpression} : m.HasType (A :: Δ) B → m.abs.HasType Δ (A.arr B)
- pair {Δ : ModuleContext} {A B : ModuleTypeRep} {m n : ModuleExpression} : m.HasType Δ A → n.HasType Δ B → (m.pair n).HasType Δ (A.prod B)
- unit {Δ : ModuleContext} : ModuleExpression.unit.HasType Δ ModuleTypeRep.unit
Instances For
De Bruijn renaming and substitution on the untyped tree #
The untyped analogues of TypedModules.ModuleExpression.rename/substitute: since there are no type
indices, a renaming is a plain Nat → Nat and a substitution a Nat → ModuleExpression.
These are permanent. Their agreement with the intrinsic versions under erase is recorded by
the transitional erase_rename/erase_substitute* lemmas below.
Lift a de Bruijn renaming under one binder: index 0 is fixed, n+1 ↦ ρ n + 1.
Untyped analogue of liftRenaming.
Equations
- GaudisCrypt.ModuleExpression.liftRen ρ 0 = 0
- GaudisCrypt.ModuleExpression.liftRen ρ n.succ = ρ n + 1
Instances For
Apply a de Bruijn renaming to every variable, going under binders with liftRen.
Untyped analogue of TypedModules.ModuleExpression.rename.
Equations
- GaudisCrypt.ModuleExpression.rename ρ (GaudisCrypt.ModuleExpression.proc p) = GaudisCrypt.ModuleExpression.proc p
- GaudisCrypt.ModuleExpression.rename ρ (GaudisCrypt.ModuleExpression.procHoles ne p) = GaudisCrypt.ModuleExpression.procHoles ne p
- GaudisCrypt.ModuleExpression.rename ρ (GaudisCrypt.ModuleExpression.var n) = GaudisCrypt.ModuleExpression.var (ρ n)
- GaudisCrypt.ModuleExpression.rename ρ (f.app a) = (GaudisCrypt.ModuleExpression.rename ρ f).app (GaudisCrypt.ModuleExpression.rename ρ a)
- GaudisCrypt.ModuleExpression.rename ρ e.fst = (GaudisCrypt.ModuleExpression.rename ρ e).fst
- GaudisCrypt.ModuleExpression.rename ρ e.snd = (GaudisCrypt.ModuleExpression.rename ρ e).snd
- GaudisCrypt.ModuleExpression.rename ρ body.abs = (GaudisCrypt.ModuleExpression.rename (GaudisCrypt.ModuleExpression.liftRen ρ) body).abs
- GaudisCrypt.ModuleExpression.rename ρ (a.pair b) = (GaudisCrypt.ModuleExpression.rename ρ a).pair (GaudisCrypt.ModuleExpression.rename ρ b)
- GaudisCrypt.ModuleExpression.rename ρ GaudisCrypt.ModuleExpression.unit = GaudisCrypt.ModuleExpression.unit
Instances For
Lift a substitution under one binder: index 0 ↦ var 0; n+1 ↦ the weakening of σ n.
Untyped analogue of liftSubstitution.
Equations
Instances For
Apply a simultaneous substitution to every variable, going under binders with liftSubst.
Untyped analogue of substituteSimultaneously.
Equations
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ (GaudisCrypt.ModuleExpression.proc p) = GaudisCrypt.ModuleExpression.proc p
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ (GaudisCrypt.ModuleExpression.procHoles ne p) = GaudisCrypt.ModuleExpression.procHoles ne p
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ (GaudisCrypt.ModuleExpression.var n) = σ n
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ (f.app a) = (GaudisCrypt.ModuleExpression.substituteSimultaneously σ f).app (GaudisCrypt.ModuleExpression.substituteSimultaneously σ a)
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ e.fst = (GaudisCrypt.ModuleExpression.substituteSimultaneously σ e).fst
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ e.snd = (GaudisCrypt.ModuleExpression.substituteSimultaneously σ e).snd
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ body.abs = (GaudisCrypt.ModuleExpression.substituteSimultaneously (GaudisCrypt.ModuleExpression.liftSubst σ) body).abs
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ (a.pair b) = (GaudisCrypt.ModuleExpression.substituteSimultaneously σ a).pair (GaudisCrypt.ModuleExpression.substituteSimultaneously σ b)
- GaudisCrypt.ModuleExpression.substituteSimultaneously σ GaudisCrypt.ModuleExpression.unit = GaudisCrypt.ModuleExpression.unit
Instances For
Single-variable substitution map: 0 ↦ arg, n+1 ↦ var n.
Untyped analogue of variableSubstitution.
Equations
- arg.variableSubstitution 0 = arg
- arg.variableSubstitution n.succ = GaudisCrypt.ModuleExpression.var n
Instances For
Single-variable de Bruijn substitution: replace index 0 in body by arg.
Equations
- body.substitute arg = GaudisCrypt.ModuleExpression.substituteSimultaneously arg.variableSubstitution body
Instances For
The de Bruijn σ-calculus laws #
rename and substituteSimultaneously compose in the four standard ways. Both the confluence
and the normalization proof need them to push a renaming or a substitution through a
β-contractum.
Substituting into a renamed term is substituting along the composite.
Renaming a substituted term is substituting along the renamed substitution.
Two substitutions in a row are one substitution along the composite.
The identity renaming acts trivially, on any expression (no typing hypothesis needed —
contrast HasType.rename_eq_self).
Renaming commutes with β-substitution.
A simultaneous substitution commutes with β-substitution.
β-substituting into a lifted simultaneous substitution is one simultaneous substitution:
index 0 goes to arg, index n+1 to σ n.
Reduction on the untyped tree, and type preservation #
ReductionStep mirrors the intrinsic one but on raw syntax; the payoff is HasType.preservation
— what intrinsic typing gave for free must now be proved. All permanent.
The untyped tuple of procedures corresponding to an instantiation (untyped analogue of
HoleSigs.Instantiation.toModuleTuple).
Equations
Instances For
inst.toModuleExpr is closed, so renaming leaves it alone.
inst.toModuleExpr is closed, so substitution leaves it alone.
Non-deterministic single-step reduction on untyped module expressions.
- beta {body arg : ModuleExpression} : (body.abs.app arg).ReductionStep (body.substitute arg)
- appL {f f' arg : ModuleExpression} : f.ReductionStep f' → (f.app arg).ReductionStep (f'.app arg)
- appR {f arg arg' : ModuleExpression} : arg.ReductionStep arg' → (f.app arg).ReductionStep (f.app arg')
- lam {body body' : ModuleExpression} : body.ReductionStep body' → body.abs.ReductionStep body'.abs
- pairL {a a' b : ModuleExpression} : a.ReductionStep a' → (a.pair b).ReductionStep (a'.pair b)
- pairR {a b b' : ModuleExpression} : b.ReductionStep b' → (a.pair b).ReductionStep (a.pair b')
- fstPair {a b : ModuleExpression} : (a.pair b).fst.ReductionStep a
- fst {e e' : ModuleExpression} : e.ReductionStep e' → e.fst.ReductionStep e'.fst
- sndPair {a b : ModuleExpression} : (a.pair b).snd.ReductionStep b
- snd {e e' : ModuleExpression} : e.ReductionStep e' → e.snd.ReductionStep e'.snd
- delta {holes : HoleSigs} {sigs : ProcedureSignature} (ne : holes.NonEmpty) (proc : ProcedureWithHoles holes sigs) (inst : holes.Instantiation) : ((procHoles ne proc).app inst.toModuleExpr).ReductionStep (ModuleExpression.proc (proc.instantiate inst))
Instances For
A de Bruijn renaming ρ maps context Δ into Γ if every Δ-index has a Γ-index of
the same type sitting at the renamed position.
Equations
- GaudisCrypt.ModuleExpression.HasType.IsRenaming Δ Γ ρ = ∀ {n : ℕ} (h : n < List.length Δ), ∃ (h' : ρ n < List.length Γ), Γ[ρ n] = Δ[n]
Instances For
Weakening: prepending a binder is the renaming Nat.succ.
A context renaming lifts under one binder to liftRen ρ.
Renaming preserves typing along a context renaming.
A substitution σ maps Δ into Γ if it sends every Δ-index to a Γ-typed term.
Equations
- GaudisCrypt.ModuleExpression.HasType.IsSubst Δ Γ σ = ∀ {n : ℕ} (h : n < List.length Δ), (σ n).HasType Γ Δ[n]
Instances For
A well-typed substitution lifts under one binder to liftSubst σ.
Simultaneous substitution preserves typing along a well-typed substitution.
Single-variable substitution preserves typing (the β-substitution lemma).
A term only mentions the variables its context declares, so a substitution that is the
identity on those leaves it alone — whatever it does to the indices beyond Δ. For a closed
term (Δ = []) the hypothesis is vacuous: Module.substituteSimultaneously_expression.
The same for a renaming: a term only mentions the variables its context declares, so a renaming
that is the identity on those leaves it alone. For a closed term (Δ = []) the hypothesis is
vacuous: Module.rename_expression.
Type preservation (subject reduction): reduction preserves the typing judgment. This is
the extrinsic replacement for what the intrinsic TypedModules.ModuleExpression guaranteed by
construction.
Equations
Instances For
Normal forms on the untyped tree #
Untyped analogues of IsProcHoles/IsProcTuple/Normal/Neutral/NormalClosed. Permanent;
mirror the intrinsic versions structurally.
m is a right-nested tuple of hole-free procedures (.unit, or .pair (.proc _) rest
with IsProcTuple rest) — the ground arguments a procedure-with-holes accepts.
Equations
Instances For
Equations
- GaudisCrypt.ModuleExpression.unit.instDecidableIsProcTuple = isTrue trivial
- ((GaudisCrypt.ModuleExpression.proc a_2).pair rest).instDecidableIsProcTuple = match rest.instDecidableIsProcTuple with | isTrue h => isTrue h | isFalse h => isFalse h
- (GaudisCrypt.ModuleExpression.unit.pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- ((GaudisCrypt.ModuleExpression.var a_2).pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- ((a_2.app a_3).pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- (a_2.fst.pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- (a_2.snd.pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- (a_2.abs.pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- ((a_2.pair a_3).pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- ((GaudisCrypt.ModuleExpression.procHoles a_2 a_3).pair rest).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- (GaudisCrypt.ModuleExpression.proc a).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- (GaudisCrypt.ModuleExpression.var a).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- (a.app a_1).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- a.fst.instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- a.snd.instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- a.abs.instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
- (GaudisCrypt.ModuleExpression.procHoles a a_1).instDecidableIsProcTuple = isFalse GaudisCrypt.ModuleExpression.instDecidableIsProcTuple._proof_1
Beta-normal form: no redex anywhere.
- neutral {e : ModuleExpression} : e.Neutral → e.Normal
- abs {body : ModuleExpression} : body.Normal → body.abs.Normal
- pair {a b : ModuleExpression} : a.Normal → b.Normal → (a.pair b).Normal
- proc {sig : ProcedureSignature} {p : Procedure sig} : (ModuleExpression.proc p).Normal
- procHoles {holes : HoleSigs} {sig : ProcedureSignature} {ne : holes.NonEmpty} {p : ProcedureWithHoles holes sig} : (ModuleExpression.procHoles ne p).Normal
- unit : ModuleExpression.unit.Normal
Instances For
Neutral form: no outermost redex — the head is a variable, or a procedure-with-holes applied to a normal non-proc-tuple (so the δ-rule is stuck).
- var {n : ℕ} : (ModuleExpression.var n).Neutral
- app {f arg : ModuleExpression} : f.Neutral → arg.Normal → (f.app arg).Neutral
- appProcHoles {holes : HoleSigs} {sig : ProcedureSignature} {arg : ModuleExpression} {p : ProcedureWithHoles holes sig} (ne : holes.NonEmpty) : arg.Normal → ¬arg.IsProcTuple → ((procHoles ne p).app arg).Neutral
- fst {e : ModuleExpression} : e.Neutral → e.fst.Neutral
- snd {e : ModuleExpression} : e.Neutral → e.snd.Neutral
Instances For
Equations
Equations
Normal form of a closed term. Neutral terms cannot occur closed (they need a free
variable), so there are fewer cases than Normal; the abs body is still general
Normal (it lives under one binder).
- proc {sig : ProcedureSignature} {p : Procedure sig} : (ModuleExpression.proc p).NormalClosed
- procHoles {holes : HoleSigs} {sig : ProcedureSignature} {ne : holes.NonEmpty} {p : ProcedureWithHoles holes sig} : (ModuleExpression.procHoles ne p).NormalClosed
- abs {body : ModuleExpression} : body.Normal → body.abs.NormalClosed
- pair {a b : ModuleExpression} : a.NormalClosed → b.NormalClosed → (a.pair b).NormalClosed
- unit : ModuleExpression.unit.NormalClosed
Instances For
Shape predicates for call-by-value reduction (untyped analogues) #
Typing is preserved along multi-step reduction.
Equations
- m.Stuck = ¬∃ (n : GaudisCrypt.ModuleExpression), m.ReductionStep n
Instances For
Whatever reduces has a redex, so it is not normal. Every case is closed by inverting
the Normal/Neutral derivation: a redex head (.abs, .pair under .fst/.snd,
a .procHoles applied to a proc-tuple) is never neutral, and the congruence cases
contradict the induction hypothesis on the reducing subterm.
m terminates: it multi-step reduces to some normal form.
Equations
- m.Terminating = ∃ (n : GaudisCrypt.ModuleExpression), n.Stuck ∧ m.MultiStepReduction n
Instances For
Convertibility: the equivalence relation generated by ReductionStep, i.e. its
reflexive, symmetric, transitive closure.
Equations
Instances For
β-normal form: a chosen normal form reachable from m when it terminates, else unit.
Equations
- m.reduce = if h : m.Terminating then Exists.choose h else Set.Nonempty.some ⋯
Instances For
Multi-step reduction refines convertibility.
reduce m is always convertible to m (a reduct if terminating, a class rep otherwise).
reduce is constant on a convertible class of non-terminating expressions. (Not private:
ModuleExpression.lean needs it for reduce_idempotent/confluence.)
A well-typed proc-tuple is the module-expression image of some instantiation (the existence
content of procTupleLookup/procTupleLookup_toModuleTuple, but as a Prop).
Progress for well-typed (possibly open) terms: a non-normal well-typed term reduces.
A normal well-typed closed term is closed-normal.
Misc #
A well-typed closed term is never neutral.
app is a congruence for multi-step reduction.
abs is a congruence for multi-step reduction.
fst is a congruence for multi-step reduction.
snd is a congruence for multi-step reduction.
pair is a congruence for multi-step reduction.
A pair of stuck expressions is stuck: pair can only step via pairL/pairR.
A well-typed closed normal term of product type is a pair.
Tactics #
moduletyping #
Syntax-directed closer for ModuleExpression.HasType m Δ T: peels off the HasType
constructor matching m's head, leaves whatever it cannot close (e.g. an unprovable
.var bound) as an open goal. See moduletyping! for a variant that fails loudly
instead.
Equations
- GaudisCrypt.tacticModuletyping = Lean.ParserDescr.node `GaudisCrypt.tacticModuletyping 1024 (Lean.ParserDescr.nonReservedSymbol "moduletyping" false)
Instances For
moduletyping, but fails loudly instead of leaving unclosed goals open. Not fail-fast:
it still runs the core script to completion (closing everything it can) before reporting,
one "Cannot show ‹original goal›: ‹reason›" line per leftover goal, with the reason
coming from describeStuckModuleTypingGoal.
Equations
- GaudisCrypt.tacticModuletyping! = Lean.ParserDescr.node `GaudisCrypt.tacticModuletyping! 1024 (Lean.ParserDescr.nonReservedSymbol "moduletyping!" false)
Instances For
normalmodule #
Syntax-directed closer for Normal m / Neutral m: peels the constructor matching m's
head, disambiguating .app's two constructors by checking whether the function side is a
literal .procHoles node, and leaves whatever it cannot close open. Fails outright if it
cannot make even one step of progress (e.g. Neutral (.pair _ _), which no constructor can
ever produce) rather than silently no-op'ing. See normalmodule! for a variant that fails
loudly with a diagnosed reason on every unclosed goal, not just a wholly-stuck one.
Equations
- GaudisCrypt.tacticNormalmodule = Lean.ParserDescr.node `GaudisCrypt.tacticNormalmodule 1024 (Lean.ParserDescr.nonReservedSymbol "normalmodule" false)
Instances For
normalmodule, but fails loudly instead of leaving unclosed goals open. Not fail-fast: it
still runs the core script to completion (closing everything it can) before reporting, one
"Cannot show ‹original goal›: ‹reason›" line per leftover goal, with the reason coming from
describeStuckNormalModuleGoal.
Equations
- GaudisCrypt.tacticNormalmodule! = Lean.ParserDescr.node `GaudisCrypt.tacticNormalmodule! 1024 (Lean.ParserDescr.nonReservedSymbol "normalmodule!" false)