HOL theorem prover in MoonBit
| Package | Key files | Description |
|---|---|---|
| foundation/ | lib.mbt, subst.mbt | Mergesort with deduplication; substitution infrastructure |
| types/ | type.mbt, match.mbt, env.mbt | HOL types (simply-typed lambda calculus) + type matching |
| terms/ | term.mbt, aconv.mbt, match.mbt, subst.mbt, ... | HOL terms (locally nameless) + term matching |
| logic/ | kernel.mbt, bool_syntax.mbt, equal.mbt | LCF kernel, connectives defined from equality, derived rules |
///|
enum Type {
TyVar(String) // type variable: enables polymorphism
TyApp(String, Array[Type]) // type constructor applied to arguments
}///|
test "types: creating basic types" {
@hol.Type::reset_table()
@hol.Term::reset_table()
// Primitive types
let bool_ty = @types.bool_ty() // TyApp("bool", [])
let ind_ty = @types.ind_ty() // TyApp("ind", [])
assert_eq(@hol.Type::pprint(bool_ty), "bool")
assert_eq(@hol.Type::pprint(ind_ty), "ind")
// Type variables
let a = @types.mk_var("'a")
let b = @types.mk_var("'b")
assert_eq(@hol.Type::pprint(a), "'a")
assert_true(a.is_var())
assert_false(b.is_type())
// Function types: 'a -> 'b
let fun_ty = @types.mk_fun(a, b)
assert_eq(@hol.Type::pprint(fun_ty), "'a --> 'b")
assert_true(fun_ty.is_fun())
let (dom, rng) = fun_ty.dest_fun()
assert_eq(@hol.Type::pprint(dom), "'a")
assert_eq(@hol.Type::pprint(rng), "'b")
}///|
test "types: registering list and building list types" {
@hol.Type::reset_table()
@hol.Term::reset_table()
@hol.Type::new_type("list", 1)
let a = @types.mk_var("'a")
let list_a = @hol.Type::mk_type("list", [a])
assert_eq(@hol.Type::pprint(list_a), "'a list")
// Nested: ('a -> bool) list
let pred_list = @hol.Type::mk_type("list", [
@types.mk_fun(a, @types.bool_ty()),
])
assert_eq(@hol.Type::pprint(pred_list), "'a --> bool list")
}///|
test "types: substitution" {
@hol.Type::reset_table()
@hol.Term::reset_table()
@hol.Type::new_type("list", 1)
let a = @types.mk_var("'a")
let b = @types.mk_var("'b")
let list_a = @hol.Type::mk_type("list", [a])
// Substitute 'a := bool
let sigma : @hol.TypeSubst = Subst(pairs=[(a, @types.bool_ty())])
let result = list_a.subst(sigma)
assert_eq(@hol.Type::pprint(result), "bool list")
// Substitute in a function type: ('a -> 'b) becomes (bool -> ind)
let fun_ab = @types.mk_fun(a, b)
let sigma2 : @hol.TypeSubst = Subst(pairs=[
(a, @types.bool_ty()),
(b, @types.ind_ty()),
])
let result2 = fun_ab.subst(sigma2)
assert_eq(@hol.Type::pprint(result2), "bool --> ind")
}///|
test "types: matching" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let bool_ty = @types.bool_ty()
let empty_state : @hol.TypeMatchState = { subst: Subst(), tyvars: [] }
// Match 'a against bool => { 'a := bool }
let s = a.match_type(bool_ty, empty_state).subst
let bound = s.lookup(a)
assert_true(bound == Some(bool_ty))
// Match ('a -> 'a) against (bool -> bool) => { 'a := bool }
let fun_aa = @types.mk_fun(a, a)
let fun_bb = @types.mk_fun(bool_ty, bool_ty)
let s2 = fun_aa.match_type(fun_bb, empty_state).subst
assert_true(s2.lookup(a) == Some(bool_ty))
}///|
enum Term {
FVar(String, Type) // free (named) variable with its type
BVar(Int) // bound variable (de Bruijn index)
Const(String, Type) // declared constant (e.g., "=")
App(Term, Term) // function application
Abs(Term, Term) // lambda abstraction
}// \x. x and \y. y both become Abs(_, BVar(0))///|
test "terms: free variables and constants" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
// Free variables carry a name and a type
let p = @terms.mk_var("p", bool_ty)
guard p is FVar(name, ty) else { fail("expected FVar") }
assert_eq(name, "p")
assert_true(ty == bool_ty)
// The only primitive constant: polymorphic equality = : 'a -> 'a -> bool
let a = @types.mk_var("'a")
let eq_ty = @types.mk_fun(a, @types.mk_fun(a, bool_ty))
let eq_const = @hol.Term::mk_const("=", eq_ty)
assert_true(eq_const is Const(_, _))
guard eq_const is Const(cname, _) else { fail("expected constant") }
assert_eq(cname, "=")
}///|
test "terms: abstraction roundtrip" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
// Build the identity function: \x. x
let id_fn = @hol.Term::mk_abs(x, x)
assert_true(id_fn is Abs(_, _))
// Destruct it back
let (bv, body) = id_fn.dest_abs()
// The binder variable and body should pretty-print the same (identity)
assert_eq(@hol.Term::pprint(body), @hol.Term::pprint(bv))
// The type of \x. x is 'a -> 'a
let id_ty = id_fn.type_of()
assert_eq(@hol.Type::pprint(id_ty), "'a --> 'a")
}///|
test "terms: application and type_of" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
// Build the application (\x. x) y
let id_fn = @hol.Term::mk_abs(x, x)
let app = @terms.mk_app(id_fn, y)
assert_true(app is App(_, _))
// The type of (\x. x) y should be 'a
let app_ty = app.type_of()
assert_true(app_ty == a)
// Build an equation: x = y
let eq = @hol.Term::mk_eq(x, y)
assert_true(@hol.Term::is_eq(eq))
let (lhs, rhs) = @hol.Term::dest_eq(eq)
assert_true(lhs == x)
assert_true(rhs == y)
}///|
test "terms: alpha-convertibility" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
// \x. x and \y. y are alpha-convertible
let abs_x = @hol.Term::mk_abs(x, x)
let abs_y = @hol.Term::mk_abs(y, y)
assert_true(abs_x.aconv(abs_y))
// But x and y (as free variables) are NOT alpha-convertible
assert_false(x.aconv(y))
}///|
test "terms: pretty-printing" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
let p = @terms.mk_var("p", bool_ty)
let q = @terms.mk_var("q", bool_ty)
// A simple equation: p = q
let eq = @hol.Term::mk_eq(p, q)
assert_eq(@hol.Term::pprint(eq), "= p q :: bool")
// The identity on booleans: (\p. p)
let id_bool = @hol.Term::mk_abs(p, p)
assert_eq(@hol.Term::pprint(id_bool), "(\\p. p) :: bool --> bool")
}///|
test "subst: type instantiation on terms" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let bool_ty = @types.bool_ty()
// Build polymorphic equality: = : 'a -> 'a -> bool
let eq_ty = @types.mk_fun(a, @types.mk_fun(a, bool_ty))
let eq_poly = @hol.Term::mk_const("=", eq_ty)
// Instantiate 'a := bool
let sigma : @hol.TypeSubst = Subst(pairs=[(a, bool_ty)])
let eq_bool = eq_poly.inst(sigma)
// The resulting type should be bool -> bool -> bool
let result_ty = eq_bool.type_of()
let expected = @types.mk_fun(bool_ty, @types.mk_fun(bool_ty, bool_ty))
assert_true(result_ty == expected)
}///|
test "subst: term substitution" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
let p = @terms.mk_var("p", bool_ty)
let q = @terms.mk_var("q", bool_ty)
// Build p = p, then substitute p := q to get q = q
let eq_pp = @hol.Term::mk_eq(p, p)
let sigma : @hol.TermSubst = Subst(pairs=[(p, q)])
let eq_qq = eq_pp.subst(sigma)
let (lhs, rhs) = @hol.Term::dest_eq(eq_qq)
assert_true(lhs == q)
assert_true(rhs == q)
}///|
test "subst: term matching" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let bool_ty = @types.bool_ty()
// Pattern: variable x of type 'a
let x = @terms.mk_var("x", a)
// Observation: variable p of type bool
let p = @terms.mk_var("p", bool_ty)
// match x against p => { x := p } and { 'a := bool }
let (tm_s, ty_s) = x.match_term(p)
assert_eq([..tm_s].length(), 1)
assert_eq([..ty_s].length(), 1)
}T := (\p. p) = (\p. p)
/\ := \p q. (\f. f p q) = (\f. f T T)
==> := \p q. (p /\ q) = p
forall:= \P. P = (\x. T)
exist := \P. forall q. (forall x. P x ==> q) ==> q
\/ := \p q. forall r. (p ==> r) ==> (q ==> r) ==> r
F := forall p. p
~ := \p. p ==> F
?! := \P. exist P /\ forall x y. P x /\ P y ==> x = y///|
test "bool_syntax: implication and negation" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
let p = @terms.mk_var("p", bool_ty)
let q = @terms.mk_var("q", bool_ty)
// Build p ==> q
let imp = @hol.BoolSyntax::mk_imp(p, q)
assert_true(@hol.BoolSyntax::is_imp(imp))
let (ant, conseq) = @hol.BoolSyntax::dest_imp(imp)
assert_true(ant == p)
assert_true(conseq == q)
// Build ~p
let neg = @hol.BoolSyntax::mk_neg(p)
assert_true(@hol.BoolSyntax::is_neg(neg))
// dest_neg returns the inner term (not the whole negation)
let inner = @hol.BoolSyntax::dest_neg(neg)
assert_true(inner == p)
}///|
test "bool_syntax: quantifiers" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
let x = @terms.mk_var("x", bool_ty)
let p = @terms.mk_var("p", bool_ty)
// Build: forall x. x ==> p
// Internally this is App(forall_inst, Abs(x, x ==> p))
let body = @hol.BoolSyntax::mk_imp(x, p)
let fa = @hol.BoolSyntax::mk_forall(x, body)
assert_true(fa is App(_, _))
// The type of a quantified formula is bool
assert_true(fa.type_of() == bool_ty)
// Build: exist x. x ==> p
let ex = @hol.BoolSyntax::mk_exists(x, body)
assert_true(ex is App(_, _))
assert_true(ex.type_of() == bool_ty)
}///|
test "bool_syntax: conjunction and disjunction" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
let p = @terms.mk_var("p", bool_ty)
let q = @terms.mk_var("q", bool_ty)
// Build p /\ q
let conj = @hol.BoolSyntax::mk_conj(p, q)
assert_true(@hol.BoolSyntax::is_conj(conj))
let (cl, cr) = @hol.BoolSyntax::dest_conj(conj)
assert_true(cl == p)
assert_true(cr == q)
// Build p \/ q
let disj = @hol.BoolSyntax::mk_disj(p, q)
assert_true(@hol.BoolSyntax::is_disj(disj))
let (dl, dr) = @hol.BoolSyntax::dest_disj(disj)
assert_true(dl == p)
assert_true(dr == q)
}refl t |- t = t
assume phi phi |- phi
eqMp (A1 |- phi = psi) (A2 |- phi) A1 U A2 |- psi
absThm v (A |- t = u) A |- (\v. t) = (\v. u)
appThm (A1 |- f = g) (A2 |- x = y) A1 U A2 |- f x = g y
deductAntisym (A1 |- phi) (A2 |- psi) (A1\{psi}) U (A2\{phi}) |- phi = psi
termSubst sigma (A |- phi) A[sigma] |- phi[sigma]
typeSubst sigma (A |- phi) A[sigma] |- phi[sigma]
betaConv (\v. t) u |- (\v. t) u = t[u/v]
defineConst c t |- c = t///|
test "kernel: refl" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
// |- x = x
let th = @hol.Kernel::refl(x)
assert_eq(th.hyps.length(), 0) // no hypotheses
let (lhs, rhs) = @hol.Term::dest_eq(th.concl)
assert_true(lhs == x)
assert_true(rhs == x)
}///|
test "kernel: assume" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let p = @terms.mk_var("p", @types.bool_ty())
// p |- p
let th = @hol.Kernel::assume_(p)
assert_eq(th.hyps.length(), 1)
assert_true(th.hyps[0] == p)
assert_true(th.concl == p)
}///|
test "kernel: eqMp" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let p = @terms.mk_var("p", @types.bool_ty())
// |- p = p (from refl) and p |- p (from assume)
let th_eq = @hol.Kernel::refl(p) // |- p = p
let th_p = @hol.Kernel::assume_(p) // p |- p
// p |- p (by eqMp with trivial equation)
let th = @hol.Kernel::eqMp(th_eq, th_p)
assert_true(th.concl == p)
assert_eq(th.hyps.length(), 1)
}///|
test "kernel: appThm" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let f = @terms.mk_var("f", @types.mk_fun(a, a))
// |- f = f and |- x = x
let th_f = @hol.Kernel::refl(f)
let th_x = @hol.Kernel::refl(x)
// |- f x = f x
let th = @hol.Kernel::appThm(th_f, th_x)
let (lhs, rhs) = @hol.Term::dest_eq(th.concl)
assert_true(lhs.aconv(rhs))
}///|
test "kernel: absThm" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
// |- x = x
let th = @hol.Kernel::refl(x)
// |- (\x. x) = (\x. x)
let abs_th = @hol.Kernel::absThm(x, th)
let (lhs, rhs) = @hol.Term::dest_eq(abs_th.concl)
assert_true(lhs is Abs(_, _))
assert_true(lhs.aconv(rhs))
}///|
test "kernel: deductAntisym" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let p = @terms.mk_var("p", @types.bool_ty())
let q = @terms.mk_var("q", @types.bool_ty())
// p |- p and q |- q
let th_p = @hol.Kernel::assume_(p)
let th_q = @hol.Kernel::assume_(q)
// {p}\{q} U {q}\{p} |- p = q => {p, q} |- p = q
let th = @hol.Kernel::deductAntisym(th_p, th_q)
let (lhs, rhs) = @hol.Term::dest_eq(th.concl)
assert_true(lhs == p)
assert_true(rhs == q)
// Both p and q remain as hypotheses (since p != q)
assert_eq(th.hyps.length(), 2)
}///|
test "kernel: betaConv" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
// (\x. x) applied to y => |- (\x. x) y = y
let id_fn = @hol.Term::mk_abs(x, x)
let th = @hol.Kernel::betaConv(id_fn, y)
let (_, rhs) = @hol.Term::dest_eq(th.concl)
assert_true(rhs == y)
// More interesting: (\x. x = x) applied to y => |- (\x. x = x) y = (y = y)
let eq_self = @hol.Term::mk_eq(x, x)
let pred = @hol.Term::mk_abs(x, eq_self)
let th2 = @hol.Kernel::betaConv(pred, y)
let (_, rhs2) = @hol.Term::dest_eq(th2.concl)
// rhs2 should be y = y
let (eq_l, eq_r) = @hol.Term::dest_eq(rhs2)
assert_true(eq_l == y)
assert_true(eq_r == y)
}///|
test "kernel: defineConst" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
// Define id := \x. x
let id_body = @hol.Term::mk_abs(x, x)
let th = @hol.Kernel::defineConst("tutorial_id", id_body)
// The theorem is |- tutorial_id = (\x. x)
assert_eq(th.hyps.length(), 0)
let (lhs, rhs) = @hol.Term::dest_eq(th.concl)
assert_true(lhs is Const(_, _))
assert_true(rhs.aconv(id_body))
}///|
test "kernel: termSubst and typeSubst" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let bool_ty = @types.bool_ty()
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
// |- x = x (via refl)
let th = @hol.Kernel::refl(x)
// termSubst {x := y} gives |- y = y
let s : @hol.TermSubst = Subst(pairs=[(x, y)])
let th2 = @hol.Kernel::termSubst(s, th)
let (lhs, rhs) = @hol.Term::dest_eq(th2.concl)
assert_true(lhs == y)
assert_true(rhs == y)
// typeSubst {'a := bool} on |- x = x gives |- x:bool = x:bool
let ts : @hol.TypeSubst = Subst(pairs=[(a, bool_ty)])
let th3 = @hol.Kernel::typeSubst(ts, th)
let result_ty = th3.concl.type_of()
assert_true(result_ty == bool_ty)
}///|
test "derived: sym" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
// x = y |- x = y
let eq_xy = @hol.Term::mk_eq(x, y)
let th = @hol.Kernel::assume_(eq_xy)
// x = y |- y = x
let sym_th = @hol.Equal::sym(th)
let (lhs, rhs) = @hol.Term::dest_eq(sym_th.concl)
assert_true(lhs == y)
assert_true(rhs == x)
}// Given: A |- s = t
//
// 1. refl(s) gives |- s = s
// 2. apTerm((= s), th) gives A |- (= s s) = (= s t)
// i.e., A |- (s = s) = (s = t)
// 3. eqMp with the |- s = s from step 1 yields A |- t = s
//
// The actual code uses appThm and the partially-applied equality operator
// to achieve this in a compact way.///|
test "derived: trans" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
let z = @terms.mk_var("z", a)
// x = y |- x = y and y = z |- y = z
let th1 = @hol.Kernel::assume_(@hol.Term::mk_eq(x, y))
let th2 = @hol.Kernel::assume_(@hol.Term::mk_eq(y, z))
// {x = y, y = z} |- x = z
let th = @hol.Equal::trans(th1, th2)
let (lhs, rhs) = @hol.Term::dest_eq(th.concl)
assert_true(lhs == x)
assert_true(rhs == z)
}///|
test "derived: apTerm and apThm" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
let f = @terms.mk_var("f", @types.mk_fun(a, a))
let g = @terms.mk_var("g", @types.mk_fun(a, a))
// x = y |- x = y
let th_xy = @hol.Kernel::assume_(@hol.Term::mk_eq(x, y))
// apTerm f (x = y |- x = y) => x = y |- f x = f y
let th1 = @hol.Equal::apTerm(f, th_xy)
let (lhs1, rhs1) = @hol.Term::dest_eq(th1.concl)
assert_true(lhs1 is App(_, _))
assert_true(rhs1 is App(_, _))
// f = g |- f = g
let th_fg = @hol.Kernel::assume_(@hol.Term::mk_eq(f, g))
// apThm x (f = g |- f = g) => f = g |- f x = g x
let th2 = @hol.Equal::apThm(x, th_fg)
let (lhs2, rhs2) = @hol.Term::dest_eq(th2.concl)
assert_true(lhs2 is App(_, _))
assert_true(rhs2 is App(_, _))
}///|
test "derived: mkBinop" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
let p1 = @terms.mk_var("p1", bool_ty)
let p2 = @terms.mk_var("p2", bool_ty)
let q1 = @terms.mk_var("q1", bool_ty)
let q2 = @terms.mk_var("q2", bool_ty)
// p1 = p2 |- p1 = p2 and q1 = q2 |- q1 = q2
let lth = @hol.Kernel::assume_(@hol.Term::mk_eq(p1, p2))
let rth = @hol.Kernel::assume_(@hol.Term::mk_eq(q1, q2))
// Use the implication connective as the binary operator
let op = @hol.BoolSyntax::implication()
let th = @hol.Equal::mkBinop(op, lth, rth)
// Result: |- (==> p1 q1) = (==> p2 q2)
let (lhs, rhs) = @hol.Term::dest_eq(th.concl)
assert_true(@hol.BoolSyntax::is_imp(lhs))
assert_true(@hol.BoolSyntax::is_imp(rhs))
}///|
test "derived: alpha" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let a = @types.mk_var("'a")
let x = @terms.mk_var("x", a)
let y = @terms.mk_var("y", a)
// \x. x and \y. y are alpha-equivalent
let t1 = @hol.Term::mk_abs(x, x)
let t2 = @hol.Term::mk_abs(y, y)
// |- (\x. x) = (\y. y)
let th = @hol.Equal::alpha(t1, t2)
assert_eq(th.hyps.length(), 0)
assert_true(@hol.Term::is_eq(th.concl))
}///|
test "worked example: combining packages" {
@hol.Type::reset_table()
@hol.Term::reset_table()
let bool_ty = @types.bool_ty()
let a = @types.mk_var("'a")
// --- Types ---
// We work with bool and 'a -> bool (predicates)
let pred_ty = @types.mk_fun(a, bool_ty)
assert_eq(@hol.Type::pprint(pred_ty), "'a --> bool")
// --- Terms ---
// Build the identity on booleans: \p. p
let p = @terms.mk_var("p", bool_ty)
let id_bool = @hol.Term::mk_abs(p, p)
// --- Boolean syntax ---
// Build: p ==> p
let imp_pp = @hol.BoolSyntax::mk_imp(p, p)
assert_true(@hol.BoolSyntax::is_imp(imp_pp))
// --- Kernel ---
// Prove |- id_bool = id_bool (reflexivity)
let refl_id = @hol.Kernel::refl(id_bool)
assert_eq(refl_id.hyps.length(), 0)
// Prove p |- p (assumption)
let assume_p = @hol.Kernel::assume_(p)
assert_eq(assume_p.hyps.length(), 1)
// Prove |- (\p. p) p = p (beta reduction)
let beta_th = @hol.Kernel::betaConv(id_bool, p)
let (_, beta_rhs) = @hol.Term::dest_eq(beta_th.concl)
assert_true(beta_rhs == p)
// --- Derived rules ---
// Symmetry: |- p = (\p. p) p
let sym_beta = @hol.Equal::sym(beta_th)
let (sym_l, sym_r) = @hol.Term::dest_eq(sym_beta.concl)
assert_true(sym_l == p)
assert_true(sym_r is App(_, _))
}| Package | What it provides | Key insight |
|---|---|---|
| Types | TyVar, TyApp, mk_fun, bool_ty | Simply-typed lambda calculus with a registry |
| Terms | FVar, BVar, Const, App, Abs | Locally nameless: alpha-equivalence for free |
| Substitution | Type subst, term subst, matching | How polymorphism and rewriting work |
| Bool syntax | All connectives from = alone | Definitional minimalism |
| Kernel | 10 primitive rules, abstract Thm | LCF architecture = soundness guarantee |
| Derived rules | sym, trans, apTerm, etc. | Sound by construction |
HOL theorem prover in MoonBit