Cheat Sheet#
Setup (Imports That Match the Book)
The book keeps reusable code in book/ and book/advanced/. If you run code from book/notebooks/, add the parent book/ directory to sys.path once:
from pathlib import Path
import sys
book_root = Path.cwd()
if not (book_root / "lc_core.py").exists():
# Likely running from book/notebooks/
book_root = book_root.parent
if str(book_root) not in sys.path:
sys.path.insert(0, str(book_root))
import lc_core as lc
import lc_interactive as lci
import church_core as ch
import stlc_core as st
from advanced import stlc_nbe as nbe
from advanced import dependent_codes as dep
from advanced import sequent_imp as sq
from advanced import classical_control as cc
from advanced import systemf_core as sf
Lambda Terms (AST Form)
Terms are Var, Lam, and App.
t = lc.Lam("x", lc.App(lc.Var("x"), lc.Var("y")))
print(lc.show(t))
Free vs Bound Variables
Free variables drive capture-avoidance.
t = lc.Lam("x", lc.App(lc.Var("x"), lc.Var("y")))
print("FV =", lc.free_vars(t)) # {'y'}
print("all =", lc.all_vars(t)) # {'x','y'}
Alpha-Renaming (Capture-Avoiding)
Rename a binder, but avoid capturing free variables.
capture_sensitive = lc.Lam("x", lc.App(lc.Var("x"), lc.Var("y")))
print(lc.show(lc.alpha_rename(capture_sensitive, "y"))) # freshens to y0, y1, ...
Substitution (Capture-Avoiding)
Substitute [x := r]t while avoiding capture by renaming binders when needed.
t = lc.Lam("y", lc.Var("x"))
out = lc.subst(t, "x", lc.Var("y"))
print(lc.show(out)) # (lambda y0. y)
Beta Reduction: Normal vs Applicative Order
Normal order can terminate where applicative order gets stuck reducing an argument.
omega = lc.App(
lc.Lam("x", lc.App(lc.Var("x"), lc.Var("x"))),
lc.Lam("x", lc.App(lc.Var("x"), lc.Var("x"))),
)
term = lc.App(lc.Lam("x", lc.Var("y")), omega) # (λx. y) Ω
print("normal:", lc.show(lc.normalize(lc.beta_step_normal, term, limit=12)))
print("applicative (12 steps):", lc.show(lc.normalize(lc.beta_step_applicative, term, limit=12)))
Definitional Equality (Beta + Eta + Alpha)
lc.defeq(a,b) compares normal forms up to alpha-renaming and eta.
a = lc.Lam("x", lc.App(lc.Var("f"), lc.Var("x")))
b = lc.Var("f")
print(lc.defeq(a, b)) # True (eta)
Interactive Reduction Player (HTML)
In a notebook, render an interactive step-by-step beta trace.
from IPython.display import HTML, display
expr = r"(lambda x. x) (lambda y. y)"
display(HTML(lci.format_reduction_player(expr, max_steps=8, title="β-trace")))
Church Booleans (and Lazy IF in Python)
Church booleans are functions. In eager Python, use IF_THUNK so only one branch is evaluated.
print(ch.to_bool(ch.AND(ch.TRUE)(ch.FALSE))) # False
print(ch.to_bool(ch.OR(ch.FALSE)(ch.TRUE))) # True
print(ch.to_bool(ch.NOT(ch.FALSE))) # True
x = ch.IF_THUNK(ch.TRUE)(lambda: "then")(lambda: 1 / 0)
print(x) # "then"
Church Numerals (Conversions + Arithmetic)
Numerals are iterators. Convert with from_int/to_int.
n = ch.from_int(3)
m = ch.from_int(4)
print(ch.to_int(ch.add(n)(m))) # 7
print(ch.to_int(ch.mul(n)(m))) # 12
print(ch.to_int(ch.pred(n))) # 2
print(ch.to_bool(ch.eq_church(n)(m))) # False
Recursion (Fixpoints) in Eager Python
Use the strict fixpoint Z plus IF_THUNK.
one = ch.from_int(1)
FACT = ch.Z(
lambda f: (
lambda n: ch.IF_THUNK(ch.is_zero(n))(
lambda: one
)(
lambda: ch.mul(n)(f(ch.pred(n)))
)
)
)
print(ch.to_int(FACT(ch.from_int(5)))) # 120
Curry-Howard (STLC Proof Terms)
Types are propositions; terms are proofs. Use st.check_goal to validate.
A = st.TyVar("A")
B = st.TyVar("B")
idA = st.Lam("x", A, st.Var("x"))
st.check_goal("id", idA, st.Arrow(A, A))
# Or-commutativity: (A ∨ B) -> (B ∨ A)
or_comm = st.Lam(
"p",
st.Or(A, B),
st.Case(
st.Var("p"),
"a",
st.Inr(st.Var("a"), B), # A -> Or(B,A)
"b",
st.Inl(st.Var("b"), A), # B -> Or(B,A)
),
)
st.check_goal("or_comm", or_comm, st.Arrow(st.Or(A, B), st.Or(B, A)))
Sequent Calculus (Search + Extraction + Proof Trees)
Prove a goal, extract a natural-deduction term, and render the sequent proof tree.
from IPython.display import HTML, display
A = sq.Atom("A")
B = sq.Atom("B")
goal = sq.Imp(A, sq.Imp(B, A)) # A -> (B -> A)
pf, term = sq.prove_and_extract(goal, depth=14)
print("term:", sq.show_t(term))
display(HTML(sq.render_proof_tree(pf)))
Denotational Semantics: Least Fixed Point via Kleene Chain
Approximate lfp(F) = sup_n F^n(⊥) by iterating F from the bottom function.
BOTTOM = None
def kleene_chain(F, steps=6):
f = lambda n: BOTTOM
out = [f]
for _ in range(steps):
f = F(f)
out.append(f)
return out
def FactF(f):
def g(n):
if n < 0:
return BOTTOM
if n == 0:
return 1
prev = f(n - 1)
return BOTTOM if prev is BOTTOM else n * prev
return g
for k, fk in enumerate(kleene_chain(FactF, steps=6)):
print(k, [fk(n) for n in range(7)])
NbE (Normalization by Evaluation) for STLC
Compare naive beta-rewriting traces to NbE normalization.
ex = nbe.build_basic_examples()
id_tm = ex["id_tm"]
id_ty = ex["id_ty"]
print("NbE:", nbe.show_tm(nbe.nbe(id_tm, id_ty)))
trace = nbe.beta_trace(id_tm, max_steps=4)
print("beta steps:", [nbe.show_tm(t) for t in trace])
Dependent-Type Codes (Executable Model)
PiCode is a dependent function type; SigmaCode is a dependent pair type.
Nat = dep.NatCode()
Bool = dep.BoolCode()
VecBool = lambda n: dep.VecCode(Bool, n)
exists_vec = dep.SigmaCode(Nat, lambda n: VecBool(n))
print(dep.is_in((0, []), exists_vec, depth=6))
print(dep.is_in((3, [True, False, True]), exists_vec, depth=6))
# head : Π n. Vec(Bool,n+1) -> Bool
head_ty = dep.PiCode(Nat, lambda n: dep.implies_code(VecBool(n + 1), Bool))
head = lambda n: (lambda xs: xs[0])
print(dep.is_in(head, head_ty, depth=5))
Classical Control (callcc) Patterns
Use callcc as an escape continuation (early exit, backtracking witnesses).
print(cc.first_satisfying([1, 3, 8, 9], lambda x: x % 2 == 0, default=None))
print(cc.subset_sum_first([3, 5, 6, 7, 9], 14)) # witness subset summing to 14 (or None)
System F (Explicit Polymorphism)
Type abstraction is TyLam; type application is TyApp. Full inference is not automatic, so instantiate explicitly.
id_term = sf.TyLam("a", sf.Lam("x", sf.TVar("a"), sf.Var("x")))
print("id:", sf.show_tm(id_term))
print("type:", sf.show_ty(sf.infer(id_term, {}, set())))
use = sf.App(sf.TyApp(id_term, sf.TInt()), sf.IntLit(7))
print("use:", sf.show_tm(use))
print("type:", sf.show_ty(sf.infer(use, {}, set())))