This repository is the mechanization of the work described in our POPL19 paper. It includes all of the definitions and proofs from Section 3, as claimed in Sec. 3.4 (Agda Mechanization).
These proofs are known to check under Agda 2.6.2
. The most direct, if
not the easiest, option to check the proofs is to install that version of
Agda or one compatible with it, download the code in this repo, and run
agda all.agda
at the command line.
Alternatively, we have provided a Docker file to make it easier to build that environment and check the proofs. To use it, first install Docker, make sure the Docker daemon is running, and clone this repository to your local machine. Then, at a command line inside that clone, run
docker build -t hazel-popl19 .
This may take a fair amount of time. When it finishes, run
docker run hazel-popl19
This should take less than a minute, produce a lot of output as Agda checks
each module and function, and end with either the line Finished all.
or
Loading all (/all.agdai).
to indicate success, depending on Docker-level
caching.
Most text editors that support Agda can be configured to use the version instead a Docker container instead of your host machine, so you can experiment with or evolve this code without making too much of a mess. For some example instructions, see the docker-agda repo.
All of the judgements defined in the paper are given in core.agda. The syntax is meant to mirror the on-paper notation as closely as possible, with some small variations because of the limitations of Agda syntax.
For easy reference, the proofs for the theorems in order of appearance in the paper text can be found as follows:
The extended paper with an appendix goes into more detail for some lemmas and definitions omitted from the main paper, some of which have been mechanized as well. Those can be found as follows:
[_/_]_
for terms and apply-env
for substitutions σ
.complete-consistency
on
line 19.complete-casts
on line 31.The theorem statements rely on a variety of lemmas and smaller claims or observations that aren't explicitly mentioned in the paper text. What follows is a rough description of what to expect from each source file; more detail is provided in the comments inside each.
On paper, we typically take it for granted that we can silently α-rename terms to equivalent terms whenever a collision of bound names is inconvenient. In a mechanization, we do not have that luxury and instead must be explicit in our treatment of binders in one way or another. In our development here, we assume that all terms are in an α-normal form where binders are globally not reused.
That manifests in this development where we have chosen to add premises that binders are unique within a term or disjoint between terms when needed. These premises are fairly benign, since α-equivalence tells us they can always be satisfied without changing the meaning of the term in question. Other standard approaches include using de Bruijn indices, Abstract Binding Trees, HOAS, or PHOAS to actually rewrite the terms when needed. We have chosen not to use these techniques because almost all of the theory we're interested in does not need them and their overhead quickly becomes pervasive, obfuscating the actual points of interest.
Similarly, we make explicit some premises about disjointness of contexts or variables being apart from contexts in some of the premises of some rules that would typically be taken as read in an on-paper presentation. This is a slightly generalized version of Barendrecht's convention (Barendregt, 1984), which we also used in our POPL17 mechanization for the same reason.
Since the type system for external terms is bidirectional, the judgments
defining it are mutually recursive. That means that anything type-directed
is very likely to also be mutually recursive. The grammar of internal
expressions is also mutually recursive with the definition of substitution
environments. All told, a fair number of theorems are mutually recursive as
this percolates through. We try to name things in a suggestive way, using
x-synth
and x-ana
for the two halves of a theorem named x
.
Both hole and type contexts are encoded as Agda functions from natural
numbers to optional contents. In practice these mappings are always
finite. We represent finite substitutions and substitution environments
explicitly as inductive datatypes, _,_⊢_:s:_
and env
from
core.agda respectively, taking advantage of the fact that the
base case in our semantics is always the identity substitution. This allows
us to reason about substitutions in a well-founded way that passes the Agda
termination checker.
We have benign postulates in two places:
First, we postulate function extensionality in Prelude.agda, because it is known to be independent from Agda and we use it to reason about contexts.
Second, in continuity.agda, we postulate some judgemental forms and theorems from our POPL17 mechanization in order to demonstrate the connections to it described in the paper. We also postulate some glue code that allows us to use those theorems in this work.
There are no other postulates in this development.
$ agda all.agda
on a clean clone of this
repository will recheck every proof from scratch. It is known to load
cleanly with Agda version 2.6.2
; we have not tested it on any other
version.These files give definitions and syntactic sugar for common elements of type theory (sum types, products, sigmas, etc.) and natural numbers that are used pervasively throughout the rest of the development.
weakening.agda argues the weakening properties for those judgements where we needed it in the other proofs. This is not every weakening property for every judgement, and indeed some of them do not enjoy weakening in every argument.
For example, the elaborations do not support weakening in the typing
context because the rule for substitution typing requires that the lowest
substitution be exactly the identity, not something that can be weakened
to the identity. (See the definition of STAId
on line 254 of
core.agda.) In practice, this is not a problem because you
wouldn't want to add anything there just to weaken it away, and allowing
imprecision here would break the unicity of
elaboration.
Together, these files give the canonical forms lemma for the language.
These files contain proofs of type safety for the internal language. Note that we only give a dynamic semantics for the internal language, which is related to the external language through elaboration.
preservation.agda argues that stepping preserves type assignment.
This is the main place that our assumption about α-normal terms appears: the statement of preservation makes explicit the standard on-paper convention that binders not be reused in its argument.
We also argue that our dynamics is a conservative extension in the sense that if you use it to evaluate terms that happen to have no holes in them, you get the standard type safety theorems you might expect for the restricted fragment without holes.
These files each establish smaller claims that are either not mentioned in the paper or mentioned only in passing. In terms of complexity and importance, they're somewhere between a lemma and a theorem.
binders-disjoint
acts as
expected. That judgement is defined inductively only on its left
argument; since Agda datatypes do not define functions, explicit lemmas
are needed to get the expected reduction behaivour in the right argument.ε
is an
evaluation context. As noted in core.agda, because we elide
the boxed-in-red finality premises from the stepping rules, every ε
is
trivially an evaluation context, so this proof is extremely immediate; it
would be slightly more involved if those premises were in place.holes-disjoint
judgement. Like
binders-disjoint
, holes-disjoint
is defined inductively on only its
left argument, so there's similar overhead.These files contain technical lemmas for the corresponding judgement or theorem. They are generally not surprising once stated, although it's perhaps not immediate why they're needed, and they tend to obfuscate the actual proof text. They are corralled into their own modules in an effort to aid readability.