Here is how you were taught to prove an implication. To prove “if then ”, you write suppose , you reason until you reach , and you write therefore implies , at which point the supposition is spent: it may not be used again, and the conclusion does not depend on it. To use an implication you already have, you write we have , and we have , so . And a supposition in force may be written down wherever it is wanted. Three moves, and every proof you have read in a mathematics lecture is built from them and their cousins for the other connectives.
Now take the rule for parameters that Chapter 2 licensed, write it beside (var) and (app), and rub out the programs:
A supposition in force may be written down. From
and
, conclude
. If
follows on the
supposition ,
conclude
and drop the supposition. They are the same three moves. The checker that
reads your definition f x = ... is checking a proof written the way
mathematicians write proofs, and this chapter is about the logic that makes that
exact.
3.1 The λ-calculus
In Chapter 1 a definition had a name, , and the name had to be declared before it could be used. Church’s notation does away with the name: it writes the function that takes and returns as , a term in its own right, which can be written wherever a term can be written and applied without ever being named.
Definition 3.1 (-terms). Fix a stock of variables . The terms are defined inductively: every variable is a term; if and are terms then is a term, the application of to ; if is a variable and is a term then is a term, the abstraction of over , the function that takes and returns ; and nothing else is a term. In one line,
Application groups to the left, as before, and a reaches as far to the right as it can, so is , not . We keep the ready-made integers and as constants.
You have written the third form many times. It is \x -> M in Haskell, lambda x:
M in Python, x => M in JavaScript; every def f(x): return M is one with a name
attached, and every method in Java is one with a name and a class attached. A definition
of Chapter 1
is the term ,
and the two constants are the two terms
which is why they need no longer be taken as given.
Where the notation comes from. The calculus is Alonzo Church’s. He introduced the notation at Princeton in the paper §1.3 described (1932): the was there to write the functions of a logic meant to carry the whole of mathematics. The logic did not survive and the notation did. Church used it to exhibit a question no procedure can settle (1936), Turing proved in the same year that it computes exactly what his machines compute, and Church repaired it with types (1940) — the repair your compiler inherited. Schönfinkel’s two combinators had been published in 1924 with the same object in view, and Curry spent the 1930s on them, which is why one language has two origins: the combinatory algebra of Chapter 1 is this calculus with the variables compiled away, and Theorem 3.3 below says that nothing is lost either way. That the calculus is also the heart of every programming language was noticed twenty years later.
Free, bound, and substitution. The in is the function’s parameter: the binds it, and its scope is the body . An occurrence of a variable is bound if it lies in the scope of a on that variable and free otherwise. In the first two occurrences of are bound and the last, like the , is free. The set of free variables of is defined by recursion on the shape of , one clause for each of the three forms:
- 1.
- is a variable : .
- 2.
- is an application : .
- 3.
- is an abstraction : .
The first two clauses only collect; the third is the one that does anything. Only a removes a variable from the list, and it removes only its own parameter. A term with no free variables is closed: a whole program rather than a fragment of one. Two terms that differ only in the names of their bound variables, like and , describe the same function, and we treat them as the same term: a bound variable may be renamed whenever that is convenient.
Substitution replaces the free occurrences of in by , and nothing else. In Chapter 1 this was simply replacement, because nothing in that language bound a variable; now it has to step around binders, and it too is defined by recursion on the shape of , one clause per form, except that the variable and abstraction cases each split according to whether the variable met is the very one being replaced:
- 1.
- is the variable itself: .
- 2.
- is a variable other than : .
- 3.
- is an application : .
- 4.
- is an abstraction on itself: .
- 5.
- is an abstraction on
some other variable :
Clauses 1 to 3 do what you would expect: replace the variable when you meet it, and otherwise pass the substitution down. Clause 4 stops at a because every inside it is bound: there is nothing to replace. The side condition on clause 5 is the one subtlety. Pushing under a when is free in would turn that free into a bound one and change what means, so the clause declines; the remedy is to rename the of to a letter that occurs nowhere else, and then substitute. Rename first. Chapter 4 shows what goes wrong when a substitution skips that step, and shows the same bug on the logic side, where it has been known for a century.
Running. There is one rule of computation. To apply a function to an argument, substitute the argument for the parameter:
| (3.1) |
anywhere inside a term; the pair on the left is a redex, and the step is called . That is what a function call does, and it is all a function call does. The three steps of Definition 1.3 are all instances of it: the step is two steps on , the step is three, and a definition step is one for each parameter.
Example 3.2 (the same addition, with the parameters visible). Let
The first step of Example 1.6 becomes four:
and from here the run is the one you have seen, each unfolding of a numeral now two steps and each unfolding of one. Nothing has changed but the grain. A is a parameter waiting for its argument, and is the argument arriving.
3.2 Typing
The types are those of Definition 1.9 and the contexts and judgements those of Definition 1.10. Two of the rules are Chapter 1’s, for variables and for applications. The third is the one the Deduction Theorem licensed — if follows from the hypothesis , then follows without it (Theorem 2.7) — which we may now adopt outright, since adopting it lets nothing new be typed:
| (3.2) |
Read (abs) as the checker reads a function definition: to check , add the parameter to the context with the type it is to have, check the body, and the function has the arrow type. If already declares an , rename the parameter first, since a context lists each variable once. Rule (abs) is the first rule we have met that changes the context: above the line there is one more variable in scope than below it. The constants are gone — and are now terms, and they get their types by (abs), which Exercise 3.1 asks you to check gives exactly (1.1) and (1.2). A typing is a finite list of judgements as before, with the conventions for justifications as before; the one difference from Definition 1.11 is that the lines of a single typing no longer all carry the same context.
Theorem 3.3 (-terms and combinator terms are one language). Every typed -term translates to a combinator term of the same type that runs the same way on every argument, and every typed combinator term is a typed -term of the same type.
Proof sketch. Right to left is the display above: write for and for ; their types come out as (K) and (S) by (abs), and the rules (var) and (app) are shared. Left to right: take the innermost abstraction in the term, so that contains no , and run the construction in the proof of Theorem 2.7 on the typing of in the context , with the programs left on rather than rubbed out. Its first case writes a , its second the identity and its third an , and what comes out is a term without , of the same type , which does to its argument what does. Repeat until no is left. □
So nothing is lost by the change of notation, and the checker of Chapter 1 and the checker of (3.2) accept the same programs at the same types. What is gained is that the compiler no longer has to compile anything to decide whether to say yes: it reads the definition as written.
A worked typing. Here is the typing of a program you have written
in one form or another, \f -> \g -> \x -> f (g x), in the context
.
- 1.
- (var): is in
- 2.
- (var): is in
- 3.
- (app) 1, 2
- 4.
- (var): is in
- 5.
- (app) 4, 3
- 6.
- (abs) 5
- 7.
- (abs) 6
- 8.
- (abs) 7
Lines 1 to 5 are routine: three look-ups and two uses of (app), each matching an argument’s type against the type a function expects. Lines 6 to 8 are (abs) three times, each taking the last variable out of the context and putting a on the front of the program: watch the context shrink as you read down, until at line 8 it is empty and the program is closed. This is what the compiler did, in a few microseconds, when it accepted your composition function.
Rub out the programs. Lines 1–5 are a proof of from the three hypotheses , and : three hypothesis lines and two uses of modus ponens. That is hypothetical syllogism — from and , conclude — which Chapter 2 proved from hypotheses in seven lines. Lines 6–8 discharge , then , then , and the formula on the last line,
is hypothetical syllogism as a theorem, with nothing assumed. That formula cost Chapter 2 seven lines of which the first was an instance of (II) with twenty letters and nineteen arrows; here it is found and checked in three discharges. The compiler read your program as that proof.
3.3 Conjunction, disjunction, and negation
The arrow is one connective, and a logic needs and, or and not as well. Each is a type you have used — a pair, a tagged union, the empty type — and each comes with its rules the way did. A rule that introduces a connective is one whose conclusion has that connective on the outside; on the program side it builds a value of the type, so it is a constructor. A rule that eliminates one has it on the outside of a premise; on the program side it takes a value apart, so it is a pattern match. Those two words, introduction and elimination, will organise everything that follows.
Definition 3.4 (conjunction). If
and are
formulas, so is ;
as a type it is the pair type, (A, B) in Haskell. The rules, with their terms:
To establish you establish both; to use it you take whichever half you need. A proof of a conjunction is a pair of proofs, and the two eliminations are the two projections.
Definition 3.5 (disjunction). If
and are
formulas, so is ;
as a type it is Either A B. The rules:
To establish
you establish one of them and say which. To use it you argue by cases: if you can reach
supposing
, and reach
supposing
, then you
have
— and both suppositions are discharged, like the supposition in
(I). A
proof of a disjunction is a proof of one side with a tag saying which, and the
elimination is a case expression with one branch per tag.
Definition 3.6 (falsity, and negation).
is a
formula, read false; as a type it is the type with no constructor, data Void in
Haskell, of which there are no values. It has no introduction rule and one
elimination:
for every formula . Negation is an abbreviation: is .
There is no way to establish
outright, and that is the point of it. If you ever have
in
hand you may conclude anything, which is what “from a contradiction,
anything follows” means and what absurd :: Void -> a says; and
is the claim
that from you
could reach , a
function from
to the empty type. Everything in this part that looks like a negation is an implication
into , and the
rules for are
the rules for .
(Whether
can be established from some set of hypotheses is a different question, and it is
the question of §3.6.)
Here is the whole language in one table: each connective, the type it is, how a
value of that type is built, and how it is taken apart.
Connective | Type | Introduction | Elimination |
|
|
| application |
|
|
| , |
|
| , |
|
|
| — | |
|
|
| application |
That is the table of the part, and the thing to carry away from it: the connectives of logic are the data types of a programming language, the introduction rules are their constructors, and the elimination rules are the pattern matches that take them apart. A proof is a program that builds and takes apart values of these types, and nothing else.
Example 3.7 (three small programs). Each of the following is a theorem, and here is the program that proves it.
- 1.
- : the program is . In the context , (E) gives and (E) gives ; (I) gives the pair the type ; (abs) discharges .
- 2.
- Contraposition:
The program is . In the context , two uses of (app) give , and three uses of (abs) give the type claimed, reading as and as . Chapter 4 uses this one as a line in a proof.
- 3.
- : the program is . In the context , (app) gives , and two uses of (abs) give .
In each case the program is one you could have written without knowing what it proved: a function that swaps a pair; a function that composes a test with a function; a function that hands its first argument to its second. The types were there all along.
3.4 Natural deduction
Everything so far has been about programs. Now rub the programs out. Take the rules of (3.2) and §3.3, delete every term, and keep what is left: judgements relating a set of formulas to a formula. Ten rules survive, and here they are.
Definition 3.8 (natural deduction). A derivation of from a set of formulas is a finite list of judgements , each justified by one of the following rules from judgements earlier in the list, ending in .
| (3.3) |
I write when a derivation exists, to keep it apart from the of Chapter 2 until Theorem 3.10 has shown that the two agree.
Every one of those rules is a rule you have already used, with the program deleted:
(I) is
a ,
(E) an
application, (I)
a pair, (E) a
case, (E)
the function absurd. Nothing has been added. What has changed is only what we
are looking at.
And what we are looking at is not a system invented for this book, nor one that came out of computing at all. It is Gerhard Gentzen’s, published in his doctoral thesis (1934), two years before Turing’s machine and before there was a program in the world to type. He called it natural deduction because he built it by watching: he took proofs as mathematicians actually write them and wrote down the moves they actually make. To prove an implication, suppose the antecedent. To use one, apply it. To prove a conjunction, prove both halves. To use a disjunction, argue by cases. Each connective gets rules that say how to prove a statement with that connective on the outside and how to use one — and nothing else. That is the whole design, and the rules of (3.3) are the record of it.
Example 3.9 (the two schemas, derived). Natural deduction has no axioms, so if it is to prove what the Hilbert system proves it must derive (I) and (II). It does, and the derivations are short. For (I):
- 1.
- (hyp)
- 2.
- (I) 1
- 3.
- (I) 2
For (II), with :
- 1.
- (hyp)
- 2.
- (hyp)
- 3.
- (E) 1, 2
- 4.
- (hyp)
- 5.
- (E) 4, 2
- 6.
- (E) 3, 5
- 7.
- (I) 6
- 8.
- (I) 7
- 9.
- (I) 8
Put the programs back on the second derivation — , , for the three hypotheses — and line 6 is and line 9 is , which is . The first derivation decorated is . The two constants of Chapter 1 are the two schemas, derived.
Theorem 3.10 (natural deduction and the Hilbert system prove the same things). Let be a set of formulas and a formula, all built from letters by alone — the fragment Chapter 2’s system can express. Then if and only if .
Proof. Right to left: go down a Hilbert proof from and replace each line by a derivation. An instance of (I) or (II) is replaced by its derivation from Example 3.9 (with the instance’s formulas for , , , and with added to every context, which (hyp), (E) and (I) all permit); a hypothesis line by one use of (hyp); and a modus ponens by one use of (E), which is the same rule under another name. Left to right: go down a derivation and replace each line by a Hilbert proof. A (hyp) line is a one-line proof (reflexivity, Proposition 2.6). An (E) line is a modus ponens on the two proofs already written. An (I) line concludes from , for which a Hilbert proof has already been written, and the Deduction Theorem (Theorem 2.7) rewrites that into a Hilbert proof of from alone. □
So the two systems are one logic in two notations, and the Deduction Theorem is precisely the thing that makes them so: it is the (I) rule of natural deduction, proved admissible for the Hilbert system. From here on I write for both.
Theorem 3.11 (Curry–Howard). Derivations of from in natural deduction and typings of -terms with are the same lists: erase the terms from a typing and (var), (app), (abs) become (hyp), (E), (I); decorate a derivation with terms — a distinct variable for each hypothesis, an application at each (E), a at each (I) — and it becomes a typing.
Proof. Put (3.2) beside (3.3). Rule for rule, the second display is the first with the terms removed; the contexts, the premises and the conclusions are otherwise identical. Erasure and decoration are therefore the relabelling of justifications of Theorem 2.4, with one more case, and the proof is the same walk down the list. □
Haskell Curry noticed in the 1950s that the types of his combinators were the axioms of a Hilbert system (Curry and Feys, 1958); William Howard, in a manuscript of 1969, saw that the same held of the -calculus and natural deduction, rule for rule, and that running a program corresponded to something Gentzen had already studied (Howard, 1980). The observation is called the Curry–Howard correspondence. It is the reason this chapter could be written twice over without saying anything twice, and §3.6 takes up the last part of it: what Gentzen had studied, and why he cared.
3.5 Derivations as trees
Definition 3.8 writes a derivation as a list, and carries the set along on every line. That is the right thing for a checker: each line can be checked on its own, because everything it depends on is written on it, and the programs of §3.2 can be written beside the lines. It is the wrong thing for a reader. The hypotheses are copied onto every line whether they are used there or not, and the shape of the argument — which step feeds which — has to be reconstructed from the justifications on the right. Gentzen wrote derivations the other way, as trees, and so does every book since. The hypotheses sit at the leaves, at the top, written once each. Each rule is a horizontal bar with its premises above and its conclusion below, labelled on the right with the rule used. The conclusion of the whole derivation is the single formula at the root, at the bottom. You read a tree downwards, and the shape of the tree is the shape of the argument.
The one thing a tree must record that the list records in its contexts is discharge. The rule (I) proves by using as a hypothesis and then giving it up: after the rule has fired, the conclusion no longer depends on . So a leaf of a tree is in one of two states. It is open, and the conclusion rests on it; or it has been discharged by some rule below it, and the conclusion does not. A discharged leaf is written in square brackets. Which hypotheses are open is not decoration: it is the whole content of the turnstile, and the definition has to track it.
Definition 3.12 (derivations). The derivations are defined inductively, and with each derivation we define at the same time two things: its conclusion, the formula at the root, and its set of open hypotheses , a finite set of formulas. In the clauses below, a derivation is drawn as a name with its conclusion written underneath it, and a formula in square brackets above a derivation is one that the clause discharges.
- 1.
- (hypothesis) For every formula , the one-node tree whose only node is labelled is a derivation, with conclusion and .
- 2.
- (I) If
has conclusion
, then for
every formula ,
- 3.
- (E) If
has
conclusion
and has
conclusion ,
then
- 4.
- (I)
If
concludes
and
concludes ,
then
- 5.
- (E)
If
concludes ,
then
are derivations, each with .
- 6.
- (I) If concludes , then for every formula the tree with root and immediate subtree is a derivation, and symmetrically from a derivation of ; in both cases .
- 7.
- (E) If
concludes
, and
and
both
conclude ,
then
is a derivation, with .
- 8.
- (E) If concludes , then for every formula the tree with root and immediate subtree is a derivation, with .
- 9.
- Nothing else is a derivation.
We write when some derivation has conclusion and .
Three remarks on reading that definition, each about a place where students expect something other than what it says.
Discharge takes every open leaf at once. Clause 2 removes from a set. Every open leaf labelled in is therefore discharged together, which is why the second tree below can close two copies of with one bracket. Some books let a rule discharge a chosen subset of the occurrences instead; nothing provable changes, because an undischarged copy can always be discharged by a later (I) that reintroduces the same formula.
Discharge may be vacuous. Clause 2 does not require to occur in at all. If it does not, is just , and the rule has proved from a proof of that never looked at . That is schema (I) of Chapter 2, and on the program side it is : a function that ignores its argument.
The numbers are bookkeeping. The definition says which leaves a clause discharges, so it needs no numbers. They appear in practice because a drawn tree may contain several rules that discharge, and the reader needs to see which bracket belongs to which bar. Writing at the leaf and at the bar is a labelling of the same information, not more of it.
Three trees. Here is hypothetical syllogism, which §3.4 gave as a six-line list with repeated on every line. As a tree it is three bars, and the two hypotheses are written once each; they stay open, because the conclusion depends on them.
Read it from the leaves down: suppose ; with , get ; with , get ; discharge the supposition and conclude . Its open hypotheses are , computed by clause 3 twice and clause 2 once, so the tree witnesses .
Next, one hypothesis discharged at two leaves at once, which is clause 2 working on a set. The conclusion is , with no open hypotheses at all.
On the program side that derivation is , and the two bracketed leaves are the two occurrences of the variable in its body. A variable used twice is a hypothesis discharged at two leaves; a variable used once, at one; and a parameter never used at all is a vacuous discharge.
Last, clause 7, which discharges two different hypotheses, one in each branch — the case where the bracket notation earns its keep. The conclusion is .
That is argument by cases, written out: we have ; in the case we reach , in the case we reach ; so we have it either way. The program is , and the two discharged leaves are the two branch variables and , each in scope only in its own branch — which is exactly what “one in each branch” means.
Theorem 3.13 (the two notations agree). For every set of formulas and every formula : some derivation in the sense of Definition 3.12 has conclusion and open hypotheses inside if and only if some list in the sense of Definition 3.8 derives .
Proof. Left to right, by induction on the derivation in the sense of Definition 3.12. Take as the induction hypothesis that each immediate subderivation has already been turned into a list whose last judgement is its conclusion. A one-node tree becomes the one-line list “, (hyp)”. For each other clause, concatenate the lists of the subderivations, weaken every judgement in them to the context the clause produces — permitted because Definition 3.8’s rules are stated for an arbitrary — and add one line, justified by the matching rule of (3.3). The clause’s equation for is exactly the context that rule produces: for (I) the set shrinks by on both sides, and for (E) by in one branch and in the other.
Right to left, by induction on the length of the list. Each line is replaced by a tree with conclusion and open hypotheses contained in , built from the trees already made for the lines it cites by the clause of Definition 3.12 matching its justification. A (hyp) line gives a one-node tree. Finally, if the last line is then the tree built for it has conclusion and open hypotheses inside . □
Induction over derivations. The definition buys more than a notation. Because it is inductive — nine clauses and a “nothing else”, exactly as Definition 1.1 built terms — it comes with a principle of proof, and it is the principle every theorem in the rest of this chapter uses.
To show that every derivation has a property , it is enough to show that each one-node derivation has , and that for each of clauses 2–8, if the subderivations the clause uses have then so does the derivation the clause builds.
That is rule induction, and it is the same move as induction on the natural numbers with the clauses in place of zero and successor. It is how Theorem 3.15 will show that running a program preserves its type, and how Lemma 3.18 will show that a stopped program is a constructor: in each case one checks the base and then walks once through the list of clauses. When you meet “by induction on the derivation” in any book on proof theory, this is what is meant.
We shall go on writing lists, because the checker does and because the programs can be written beside them, and we shall draw trees when the shape of an argument is the point. They are the same object described twice, and Theorem 3.13 is the licence to move between them without comment.
3.6 Normalisation
What Gentzen had studied, and what Howard saw running a program corresponded to, is this. A proof can take a detour: it can establish by (I) from proofs of and , and then at once take back out by (E); or establish by (I), discharging a supposition, and at once use it on a proof of by (E). In each case an introduction is met by its own elimination, and the proof would have been shorter without the pair. On the program side the pair is a redex, and removing it is a step of computation.
Definition 3.14 (running, for every connective). A step rewrites, anywhere inside a term, an introduction met by its elimination:
There is no rule for , because has no introduction for it to meet. A term in which no step can be taken is in normal form, and a proof with no detour is a normal proof.
These are the steps your interpreter takes: call a function, project a pair, branch on a tag. That they are also the removal of detours from proofs is the content of the correspondence, and the two theorems of Chapter 1 say the same two things about them, now for the whole language.
Theorem 3.15 (subject reduction). If and , then .
Proof sketch. The one new ingredient is a substitution lemma: if and , then . It is proved by going down the typing of and replacing every line that types by (var) with the typing of ; every later line is unchanged, since the rules look only at types. With that in hand, each step is one case. A step: came by (app) from and , and the first of these by (abs) from ; the lemma gives . A projection: came from , which came from . A case on : the conclusion came from , so , and from ; the lemma gives . A step inside a larger term replaces the lines for the subterm by lines of the same type, as in Theorem 1.16. □
Theorem 3.16 (normalisation; on loan). Every typed term runs to a normal form. Equivalently: every proof can be rewritten, by removing detours, into a normal proof of the same formula from the same hypotheses.
The program-side proof is Tait’s (1967) and the proof-side proof is Prawitz’s (1965); the two are the same proof, and Girard et al. (1989, chapter 4) give it in a form readable at the level of these notes, for and , with and needing a little more. We take it on trust, as we did in Chapter 1.
It is worth saying why Gentzen wanted this theorem, because he did not want it in order to run anything. He wanted consistency. In the 1920s Hilbert had set the programme of proving, by arguments so elementary that no one could doubt them, that mathematics could never derive a contradiction; and the elementary arguments allowed were arguments about the shapes of written symbols — no appeal to infinite collections, no appeal to what the symbols were about. A normalisation theorem is exactly such an argument, and it pays off at once. A proof with no detours in it never invents material: every formula appearing in it is a piece of the formulas at its two ends. So if you want to know whether can be proved from nothing, you need only ask what a detour-free proof of from nothing could possibly look like, and the answer is that there is no shape available for it. Consistency becomes a statement about the grammar of proofs rather than about mathematics, which is why Gentzen proved his version of this theorem — the Hauptsatz, the main theorem of his thesis — before he proved anything with it, and why Theorem 3.19 below costs us a page rather than a book.
How far that method can be pushed is the question of Chapter 11, where it meets a limit that Hilbert did not expect. For now we collect what normalisation buys here, which is a great deal, because a program that has stopped has a very particular shape.
Definition 3.17 (normal and neutral terms). A typed term is neutral if it is a variable, or an elimination applied to a neutral term: , , , , or , with neutral and the other parts in normal form. A term is in normal form, in the sense of Definition 3.14, exactly when it is an introduction form — , , or — whose parts are in normal form, or is neutral.
The “exactly when” is a small induction on the term, and the one thing it needs is types: a normal-form application has an of arrow type that is not a , and the only normal-form terms of arrow type that are not s are neutral ones; likewise a projection of a non-pair, a case on a non-tag, and an of anything, since has no introduction form at all. Exercise 3.8 asks for the cases in full. What matters is this: a neutral term is a chain of eliminations hanging off a variable, and that variable is free in it, since nothing in the chain binds it.
Lemma 3.18 (the shape of a stopped program). A closed typed term in normal form is an introduction form. Consequently there is no closed term in normal form whose type is a letter , and none whose type is .
Proof. By Definition 3.17 a normal-form term is an introduction form or neutral, and a neutral term has a free variable, its head; so a closed one is an introduction form. An introduction form has type , or by the rule that introduces it, and none of those is a letter or . □
Theorem 3.19 (consistency). There is no closed term of type : . More generally, for every letter , .
Proof. Suppose . By Theorem 3.16, runs to a normal form . By Theorem 3.15, ; and is closed, since no step of Definition 3.14 creates a free variable. So is a closed normal-form term of type , which Lemma 3.18 says does not exist. The argument for a letter is the same with for . □
Read what has been shown. The logic of this chapter does not prove everything. It does not prove , and it does not prove any bare claim : whatever it proves has an arrow, an and or an or on the outside, which is to say that it proves things about the relationships between claims and never a claim out of nothing. The disaster of §1.3 — a logic in which every statement had a proof — cannot happen here, and the reason it cannot is that every program stops and a stopped program is a constructor. The argument used nothing about what the letters mean, no table, no notion of a formula being the case. It is an argument about the shapes of strings, and it was available to Gentzen in 1934. The same question — does this system prove everything? — asked of arithmetic rather than of the bare connectives, is the subject of Chapter 11, and the answer there is of a different kind.
One more thing falls out of the shape lemma, and it is worth having now.
Corollary 3.20 (a proof of or says which). If , then or .
Proof. A closed term of type runs to a closed normal form of that type, which by Lemma 3.18 is an introduction form: with , or with . □
In this logic you cannot establish “ or ” without establishing one of them and saying which, because a proof of a disjunction is a value with a tag on it. Hold on to that; Part 2 has a use for it.
3.7 Curry’s paradox
Now the argument that closes §1.3, written out exactly, with the programs on. It needs one thing the rules do not allow, and seeing where the rules refuse is the point.
Example 3.21 (Curry’s paradox, with its program). Suppose, for the length of this example, that there were a type satisfying the equation
for some formula chosen in advance, so that a term of type could also be read as a function from to and the other way round. Then:
- 1.
- (var)
- 2.
- (var), reading as
- 3.
- (app) 2, 1
- 4.
- (abs) 3
- 5.
- line 4, reading as
- 6.
- (app) 4, 5
Rub out the programs and read the justifications: suppose ; then , since that is what says; so ; discharge the supposition and conclude ; which is ; so . It is the argument of §1.3 line for line, with (I) for “discharge”, and since was arbitrary it proves everything. And the program on line 6 is . The looping program of Chapter 1 and the proof of everything are one term, as §1.3 promised they would be.
Now look at where it fails. Lines 2 and 5 are justified by no rule: they rest on the equation , and Proposition 1.15 said why no type satisfies it — a type is a finite tree, and is strictly larger than . Take the equation away and line 2 cannot be written, so line 3 cannot, so has no type and has none, which is what the proposition said. The one fact that refused the looping program is the one fact that refuses the paradox.
Remark 3.22 (what recursion costs). Every language you use has recursion, and Exercise 1.6 modelled it by a constant with the type for every and the step . That constant is exactly what Example 3.21 needed: it lets a program feed on itself without an equation between types. With it, has every type there is, and every letter included, so Theorem 3.19 fails; and it runs for ever, so Theorem 3.16 fails with it. The two theorems go together, as they came together. Read as a logic, a language with general recursion proves everything, and a type in such a language promises only what the program does if it returns. That is not a reason to give up recursion. It is the reason the checker’s yes, in the languages you use, means “safe” and never “correct”, and the next chapter is about the gap between the two.
The -calculus with pairs, sums and the empty type is a proof system — Gentzen’s natural deduction — with the programs written beside the lines: a hypothesis is a variable, (I) is , (E) is application, and the other connectives are your data types, their introduction rules the constructors and their elimination rules the pattern matches. It proves exactly what the Hilbert system proves, and the Deduction Theorem is its (I). Running a program is removing detours from a proof, and what the checker says survives it (subject reduction). Every typed program stops (on loan), and a stopped closed program is a constructor; so the logic proves no letter and no — it does not prove everything — and a proof of or always says which. Curry’s paradox and are one term, refused by one fact: a type is not a part of itself.
Safe is not correct. Nothing we care about — a sorted list, a transitive order, a discount on every day of the sale — is proved from no hypotheses; by Theorem 3.19 nothing with a bare claim at the top ever is. Such things are proved from a specification, a set of sentences about lists, orders or sales taken as hypotheses. And a specification says for all — for all , for all — which no formula of this chapter can say.
3.8 From pseudocode to combinators
This part has three languages in it: the two constants of Chapter 1, the -calculus of this one, and the proofs that both turned out to be. They are one language, and the way to believe that is to watch something ordinary travel the whole distance. Here is factorial, as you would write it on a whiteboard.
fact(n):if n = 0 then return 1else return n * fact(n - 1)
Four things in it are not in the language of Definition 1.1: a parameter, a name that calls itself, a conditional, and arithmetic. Take them away one at a time.
The parameter becomes a . A definition with a parameter is an abstraction, which is what §3.1 was for. Writing , and for the test, the product and the predecessor, and using a boolean as its own conditional in the manner of Example 1.5, so that “if then else ” is written :
Nothing has really happened yet, because still occurs on the right-hand side, and a term may not refer to itself.
The self-reference becomes a parameter. So make it one. Abstract over the recursive call as well, and the self-reference disappears:
The term mentions nothing but its own two parameters: give it the function to call and a number, and it does one layer of the work. What we want is a term with — a fixed point of — and that is exactly what the constant of Exercise 1.6 delivers, since . So
and one step shows why that is the right definition: , whose is again — the recursive call is the fixed point unfolding once more, on demand and no sooner.
The arithmetic was combinators already. The three operations and the numerals are the ready-made constants of Chapter 1, and Theorem 1.8 says each of them is a term in and : Example 1.6 builds the numerals and addition, Exercise 1.1 multiplication, and the predecessor is a famous enough puzzle to have its own anecdote. Nothing is being smuggled in; we keep them as names only to keep the term on the page.
The s become and . One construction remains, and we already have it. The proof of the Deduction Theorem, run with the programs left on rather than rubbed out, takes a term with a parameter and returns one without (Theorem 3.3). Written as a recipe, it eliminates a single from a term containing no other, producing a term in which does not occur:
| (3.4) |
The three clauses are the three cases of that proof wearing programs: the first is the case of a line that does not use the hypothesis, and writes the that weakens it in; the second is the case of the hypothesis itself, whose proof of is ; the third is the case of a modus ponens, and writes the that pushes the hypothesis through it. The recipe works because of the guarantee that comes with it: runs to with in place of , for every .
Apply it to , innermost parameter first. Write for the body, so that , and read as the application . The third clause splits it, the first and second finish each half, and with written for the result is
twenty-six symbols, with still free in it. Eliminate from that by the same three clauses and every variable is gone:
where , and . Thirty-eight symbols, counting as its three. There is not a variable, a parameter, a conditional or a recursive call anywhere in it — only , , three arithmetic constants and juxtaposition — and applied to runs, by the two rules of Definition 1.3, to .
That is the whole of this part in one object. A program you could have written in your first week is a closed term in two constants (Chapter 1); it has a type, , and the typing that proves so is a proof (Chapter 2); the proof is the one a mathematician would write, in Gentzen’s rules, with the terms rubbed out (§3.4). Every well-typed thing you compile sits on and in exactly this way.
With one exception, and it is the one Chapter 1 warned of. The two constants did not supply the loop: had to be added, and Remark 3.22 has just said what adding it costs. Normalisation goes, so is a typed term with no normal form; and read as a proof, proves everything, included. The price of factorial is the consistency of the logic. Every language you use pays it, and pays it knowingly: the loop is worth more to a programmer than consistency is, and a programmer who wants both must say in the type where the recursion is allowed to go — which is a question for a logic with more to say than this one has.
Exercises
Exercise 3.1. Give typings of and of by the rules (3.2), and confirm that the types are those of (1.1) and (1.2). Then run and count the steps.
Exercise 3.2. Run for three steps. Then run , first blindly and then renaming the bound first, and say which answer is the one meant.
Exercise 3.3. Give natural-deduction derivations, in list form, of and of . Then write the program each one is.
Exercise 3.4. Find a closed program of type , and give its derivation.
Exercise 3.5. Find a closed program of type .
Exercise 3.6. Try to find a closed program of type , for a letter . Say where every attempt sticks. Keep your attempt; Part 2 returns to this formula.
Exercise 3.7. Theorem 3.10 turns the Hilbert proof of hypothetical syllogism in §2.6 into a derivation; decorated, it is the program with and written as -terms. That derivation has detours. Run the program to normal form and compare with §3.2.
Exercise 3.8. Prove the “exactly when” of Definition 3.17 in full: show by induction on a typed term in normal form that it is an introduction form with normal-form parts or neutral, giving the cases for and carefully, and say where the type is used.
Reading
Girard et al. (1989, chapters 2–4) is the text behind §§3.4–3.6: natural deduction, the identification of proofs with programs, conversions as the removal of detours, and the normalisation theorem proved in full for and ; it is short, free online, and the right next thing to read. Gentzen (1934) is the original, and Prawitz (1965) the book that made normal proofs a subject. Howard (1980) is the correspondence as Howard wrote it down in 1969; Wadler (2015) is the story told to programmers. Next, Chapter 4: for all, and proving from a specification. References.
- Alonzo Church. A set of postulates for the foundation of logic. Annals of Mathematics, 33(2):346–366, 1932.
- Alonzo Church. An unsolvable problem of elementary number theory. American Journal of Mathematics, 58(2):345–363, 1936.
- Alonzo Church. A formulation of the simple theory of types. Journal of Symbolic Logic, 5(2):56–68, 1940.
- Haskell B. Curry and Robert Feys. Combinatory Logic, Volume I. North-Holland, Amsterdam, 1958.
- Gerhard Gentzen. Untersuchungen über das logische Schließen I, II. Mathematische Zeitschrift, 39:176–210, 405–431, 1934. Appeared 1934–35; English translation in The Collected Papers of Gerhard Gentzen, ed. M. E. Szabo, North-Holland, 1969.
- Jean-Yves Girard, Yves Lafont, and Paul Taylor. Proofs and Types, volume 7 of Cambridge Tracts in Theoretical Computer Science. Cambridge University Press, 1989. Free online.
- William A. Howard. The formulae-as-types notion of construction. In J. P. Seldin and J. R. Hindley, editors, To H.B. Curry: Essays on Combinatory Logic, Lambda Calculus and Formalism, pages 479–490. Academic Press, London, 1980. Written in 1969.
- Dag Prawitz. Natural Deduction: A Proof-Theoretical Study. Almqvist & Wiksell, Stockholm, 1965.
- William W. Tait. Intensional interpretations of functionals of finite type I. Journal of Symbolic Logic, 32(2):198–212, 1967.
- Philip Wadler. Propositions as types. Communications of the ACM, 58(12):75–84, 2015.