Hewwo everyone!! 🐱✨ I've been wanting to write this up for a while and finally found time. This is a walkthrough of how to write intrinsically typed (also called well-scoped, well-typed) interpreters in Agda, where the Agda type system itself enforces the object language's type system. No separate type safety proof needed — it's correct by construction!
I'll assume you know basic Agda syntax and have some familiarity with dependent types. If not, go read PLFA first!! (The DeBruijn chapter is especially relevant here.)
§1 — What does "intrinsic" even mean?
There are two broad approaches to mechanising a typed language:
- Extrinsic (a.k.a. à la Church/raw terms): define an untyped AST first, then write a separate typing relation, then separately prove type safety.
- Intrinsic (a.k.a. à la Curry/typing derivations): bake the types directly into the AST so that only well-typed terms can be constructed.
With the intrinsic approach, Γ ⊢ A is the type of terms that have type A in context Γ. There are no ill-typed terms — they simply don't exist as values of this type. The Agda type-checker is doing all the work for you.
§2 — The Object Language: STLC with ℕ
We'll interpret the Simply Typed Lambda Calculus with natural numbers. First, let's define the types of our object language:
Now the intrinsically typed term representation. Notice how the type and context are indices of the _⊢_ datatype — a value of type Γ ⊢ A is by definition a term of type A in context Γ:
The beautiful thing: you simply cannot construct a term Γ ⊢ A that is ill-typed. The type indices prevent it at Agda's compile time! 🎉
§3 — The Semantic Domain
We need to interpret object-language types as Agda types. We use a function ⟦_⟧ᵀ:
§4 — The Interpreter
And here it is — the payoff! The interpreter eval maps a well-typed term in an environment to a value in the semantic domain. The type signature says it all:
Look at how tiny this is! No error cases, no Maybe, no partial functions. Every case is total and obviously terminating. The type safety guarantee isn't proved — it's witnessed by the type of eval itself.
In the next posts I'll talk about the comparison with extrinsic typing and we can discuss the trade-offs! 🐾