Okafor reviews code for a bank. A pull request from another team changes a helper that runs whenever the bank’s two offices hold different copies of the same customer’s record and one of them has to win. The helper is four hundred lines long, and its type is

pick :: a -> a -> a

The question on the review ticket is the one a bank cares about: can this change corrupt a record, by mixing the fields of two records, say, or blanking a card number? Okafor approves it after reading one line, the type.

Here is her reasoning. The type says that pick works for every type a: whatever a record is, it takes two of them and returns one. It was written without knowing what an a is, so it can’t look inside one, can’t change a field, and can’t build a new record, because nothing it was given says how. The only as it has are the two it was handed, so what it returns is one of those, as it arrived. Whatever the four hundred lines do, they can’t corrupt a record.1 The one thing left to review is which of the two it keeps, and a single test settles that.

That’s a theorem about a program, read off its type, and the type is a quantifier over types: pick has the type Πa(a→a→a). The chapter begins with the logic that quantifier belongs to.

Chapter 4’s quantifiers range over things. Let one range over propositions instead and you get second-order logic, in which a schema becomes a single theorem and the connectives ∧, ∨ and ⊥ can be defined from → and ∀. The quantifier is of a kind Russell banned, so when the logic is on the page its consistency is an open question.

Then the programs go back on, as they did in Chapter 4, and what comes out is something you already use: generics, the a in length :: [a] -> Int, with the side conditions as two compiler error messages, and in full the polymorphic λ-calculus, also called System F. Girard’s proof that its programs stop answers the logic’s question, and gives Okafor’s argument as a theorem. Pushed one step further, to a type of all types, the system collapses, as Church’s logic did in Chapter 1 before types repaired it.

5.1 Quantifying over propositions

The first-order quantifiers range over things: kinds, drivers, days. Look back at the propositional theorems, though, and you’ll find a quantifier of another kind that nobody wrote down. The theorem p→p was never about p in particular. Its proof works with any formula at all in the place of p, and that is why the Hilbert system states its axioms as schemas, with Greek letters standing for every formula.

A schema is a claim about all formulas made one instance at a time, which is Ivanova’s forty letters from Chapter 4 again. The cure is the same as before. Let a quantifier range over propositions, and write the claim once: ∀X(X→X), “whatever proposition X is, X implies X”.

The variable X needs a symbol of its own. The propositional letters p,q,r are constants: each stands for one fixed claim, and nothing can bind it. A variable for propositions is as different from a letter as the variable x is from the constant cam, so we write propositional variables differently, as X,Y,Z.

Definition 5.1 (second-order propositional logic). Fix the propositional letters p,q,r,… and, separately, a stock of propositional variables X,Y,Z,…. The formulas are

A,B::=p∣X∣A→B∣∀XA,

where ∀XA binds X in A. Free propositional variables, substitutability, and the substitution A[B/X] of a formula B for the free occurrences of X in A are defined by the clauses for first-order formulas.

The derivations are built by the clauses for hypotheses, (→I) and (→E), and two more:

DA∀XA∀Iif X is free in no formula of OH(D),
D∀XAA[B/X]∀Efor every formula B substitutable for X in A,

each with the open hypotheses of D. We write Γ⊢NJ2A for derivability in this system, second-order NJ.

The two quantifier rules are the first-order ones with propositions in the place of things: (∀I) generalises over a proposition nothing open says anything about, and (∀E) uses the result at a particular one.

Example 5.2 (two schemas, as theorems). Two schemas become single theorems: the identity, and Frege’s first axiom, schema (I) of the Hilbert system.

[X]1X→X→I,1∀X(X→X)∀I[X]1Y→X→IX→Y→X→I,1∀Y(X→Y→X)∀I∀X∀Y(X→Y→X)∀I

In each tree the (∀I) bars come after every hypothesis has been discharged, so nothing open mentions X or Y and the bars are legal. Both trees have OH=∅, so they witness ⊢NJ2∀X(X→X) and ⊢NJ2∀X∀Y(X→Y→X). An instance of the schema is now one step: (∀E) at p turns ∀X(X→X) into p→p.

5.2 The connectives, defined

The logic has → and ∀ and nothing else, and needs nothing else, because ∧, ∨ and ⊥ can be written with those two. Say what you can do with a conjunction: from A∧B you can conclude every claim that follows from A and B together. Then let that be the definition.

Proposition 5.3 (the connectives in second-order logic). Define, with X a propositional variable not free in A or B,

⊥:=∀XX,A∧B:=∀X((A→B→X)→X),A∨B:=∀X((A→X)→(B→X)→X).

With these definitions every rule of NJ for ⊥, ∧ and ∨ is derivable: for each rule, its conclusion is derivable from its premisses, discharging what the rule discharges.

Proof. Write Δ for the formulas the premisses are derived from, and C for the conclusion wanted. The variable X is chosen free in no formula of Δ, so every (∀I) below is legal.

(⊥E)

From Δ⊢NJ2∀XX, (∀E) at C gives Δ⊢NJ2C.

(∧I)

From Δ⊢NJ2A and Δ⊢NJ2B:

Δ, A→B→X⊢NJ2Xtwo (→E)sΔ⊢NJ2(A→B→X)→X(→I)Δ⊢NJ2A∧B(∀I).
(∧E1)

From Δ⊢NJ2A∧B:

Δ⊢NJ2(A→B→A)→A(∀E) at A⊢NJ2A→B→Aas in Example 5.2Δ⊢NJ2A(→E).

(∧E2) is the same with B and A→B→B.

(∨I1)

From Δ⊢NJ2A, (→E) gives Δ, A→X, B→X⊢NJ2X; two (→I)s and (∀I) give Δ⊢NJ2A∨B. (∨I2) is the same from B.

(∨E)

From Δ⊢NJ2A∨B, and the two branches Δ, A⊢NJ2C and Δ, B⊢NJ2C:

Δ⊢NJ2(A→C)→(B→C)→C(∀E) at CΔ⊢NJ2A→CandΔ⊢NJ2B→C(→I) on each branchΔ⊢NJ2Ctwo (→E)s.

□

Each connective is defined by its elimination rule:

  • A∧B says “whatever follows from A and B together, follows”;

  • A∨B says “whatever follows from A and also from B, follows”, which is proof by cases;

  • ⊥ says “everything follows”.

So propositional NJ sits inside this one, built from → and ∀ alone.

Higher-order logic

Second-order propositional logic quantifies over propositions. Higher-order logic goes the whole way: over properties of things, relations between them, properties of properties, and functions of every type. Its language is the simply typed λ-calculus itself, with a type o of propositions and a type ι of things, so that a property of things is a term of type ι→o, and a variable P of that type can be bound by ∀P. This is Church’s simple theory of types (Church, 1940), the logic for which he typed his calculus. In it, induction on the natural numbers is one sentence,

∀P(P(0)∧∀n(P(n)→P(n+1))→∀nP(n)),

where first-order logic needs a schema, one axiom for each formula.

The power has a price: Chapter 16 shows that once the quantifiers are read as ranging over all properties, no proof system whose proofs can be checked mechanically captures that reading, even for second-order logic.

Impredicativity

Look at (∀E) once more. The formula B put in for X may be any formula at all, including ∀XA itself: ∀X(X→X) may be used at ∀X(X→X), and ⊥, which is ∀XX, at ⊥. A quantifier over all propositions ranges over a collection that includes the proposition it is helping to form. Definitions of that kind are called impredicative, and Russell banned them from Principia for fear of the self-application that produced the paradoxes.

So we have to ask whether this logic is consistent. For first-order logic the answer came from programs:

  1. decorate the derivations;

  2. run them;

  3. look at what they stop at.

Do the same here.

5.3 Generics

Decorate a second-order derivation as Chapter 4 decorated a first-order one:

  • the bars for → become λ and application, as before;

  • a (∀I) bar over X becomes a type abstraction Λa.M, a program that takes a type and returns a program;

  • a (∀E) bar at B becomes an instantiation M[B], which hands it one.

The formulas become types, with the usual renaming: a propositional variable X becomes a type variable a, a type nobody has chosen yet, and ∀X becomes Πa, the Π of dependent types with types in the place of things. The two theorems of Example 5.2 decorate to

Λa.λh.h:Πa(a→a)andΛa.Λb.λh.λk.h:ΠaΠb(a→b→a).

What has come out is something you’ve been writing for years, and Okafor’s type. ΠaA is the type of a program that works for every type a, and the rules are (ΠI) and (ΠE) of Chapter 4’s dependent types with types in the place of things,

(ΠI) Γ, a⊢FM:AΓ⊢FΛa.M:ΠaA(ΠE) Γ⊢FM:ΠaAΓ⊢FM[B]:A[B/a],

where B may mention only type variables declared in Γ. The context is the same stack, and running is one more step, (Λa.M)[B]⇝M[B/a].

Example 5.4 (the identity). The first of the two programs above is the function id x = x, and its typing is the first tree of Example 5.2 with the programs on:

  1. declare a,

  2. declare h:a,

  3. pop h with (abs),

  4. pop a with (ΠI).

Using it at Int is one (ΠE), (Λa.λh.h)[Int]⇝λh.h:Int→Int, and Haskell inserts that instantiation for you at every call.

The type of length is [a] -> Int, and the a in it is a parameter, a type nobody has chosen yet. Haskell leaves the quantifier out, and writes it forall only when asked. Written in, it reads length:Πa([a]→Int), so length is a function that works for every element type at once.

Without a way to say for every you’d have three options.

  1. The first is one copy per element type,

    lengthOfIntList,   lengthOfStringList,   lengthOfBoolList,   …

    which is Ivanova’s forty letters from Chapter 4 once more.

  2. The second is to give it the type [Object] -> Int and cast on the way in and out. Java made people do that until 2004, and a wrong cast is caught only when the program runs.

  3. The third is to turn the checker off for that part of the program.

With the quantifier you write the code once and check it once, for every type, and that’s why map, filter, sort and every container you’ve used have one in their types. The logic got there first: a generic function is a proof of a schema, written once.

Remark (looking back: schemas). Chapter 1 typed K by a schema, A→B→A, and recorded in square brackets after (K) the types chosen for A and B at each use. The brackets are instantiations: K is the program Λa.Λb.λh.λk.h:ΠaΠb(a→b→a), and the typing “(K) [Int, Int]” is the term K[Int][Int]. Java’s static <A, B> declares the two type variables in the same place.

The side condition on (∀I), the one Chapter 4 ran to ⊥, turns up as an error message. Try

f :: a -> b
f x = x

and GHC answers couldn’t match expected type b with actual type a, adding that a is a rigid type variable bound by the signature.

Here’s what happened in the stack. The signature declared a and b and promised a program that works for both separately. The body can only be typed if a and b are the same, which means a hypothesis about a is still in force when the checker reaches the (ΠI) that would pop it. Rigid is the compiler’s word for declared and not last, a variable the context has fixed and you may not generalise over.

Example 5.5 (a counter). There is a Σ over types too, and it’s a module. Suppose you want a counter: a thing with a starting state, a way to step it, and a way to read off a number. You don’t want to say what the state is, because that’s the implementation’s business.

{-# LANGUAGE ExistentialQuantification #-}
data Counter = forall a. MkCounter a (a -> a) (a -> Int)

intCounter  = MkCounter (0 :: Int)   (+1)    id
listCounter = MkCounter ([] :: [()]) (():)   length

tick3 :: Counter -> Int
tick3 (MkCounter s step out) = out (step (step (step s)))

Both counters are values of the one type Counter, though one carries an Int and the other a list, and tick3 returns 3 for each without being told which. The type Counter is Σa(a×(a→a)×(a→Int)), there is a type with these three operations.

MkCounter packs a witness, the state type, as (ΣI) packed a thing. The pattern match in tick3 opens the pair, as (ΣE) did, and its result type Int doesn’t mention the state type.

A function that tried to return the counter’s state would have result type a, and GHC says that the rigid type variable a would escape its scope. It’s the condition written on (ΣE), in the compiler’s words. The implementation type may be used inside the match and may not leave it. A module boundary is a boundary because of this. It’s one side condition on one rule, not a convention anybody has to remember.2

A Counter is Ivanova’s kernel handle from Chapter 4, written as a type: a hidden state, three operations on it, and a client that may run them and may not see the state. Had the camera driver’s record been one of these, the escape condition, not a convention kept in the programmer’s head, would have stopped the microphone’s bytes being read as a camera’s.3

The generic quantifier, side conditions and all, has been at work every time the compiler has read a definition with a type variable in it. It is second-order logic with its programs on, and the next section writes the calculus down in full.

5.4 The polymorphic λ-calculus

Here is the calculus the section on generics used informally.

Definition 5.6 (polymorphic λ-calculus). Fix a stock of type variables a,b,c,…. The polymorphic types and the terms are

A,B::=a∣A→B∣ΠaA,M,N::=h∣MN∣λh.M∣Λa.M∣M[B],

where h is an evidence variable. In ΠaA and Λa.M the type variable a is bound, and A[B/a] substitutes the type B for the free occurrences of a in A, by recursion on the shape of A:

A[B/a]:={Bif A is a,Aif A is a type variable b≠a,A1[B/a]→A2[B/a]if A is A1→A2,ΠaA1if A is ΠaA1,ΠbA1[B/a]if A is ΠbA1, b≠a, b∉FV(B).

A context is a finite list of declarations of type variables a and of typings h:A, in which:

  • no variable is declared twice;

  • every type variable free in a type in the context is declared earlier than the typing that mentions it.

The judgement Γ⊢FM:A, with F for System F, is defined by the rules of Figure 5.1, and β-reduction has one rule for each kind of abstraction,

(λh.P)Q⇝P[Q/h]β(Λa.P)[B]⇝P[B/a]β

together with a rule for each place a subterm can sit, as in Chapter 3: a step inside a term is a step of the term.

The typing rules of the polymorphic lambda-calculus. Above a dotted line, the rules for implication: (var), Gamma proves h of type A whenever h of type A is in Gamma; (app), from Gamma proves M of type A to B and Gamma proves N of type A, conclude Gamma proves M N of type B; (abs), from Gamma, h of type A proves M of type B, conclude Gamma proves lambda h M of type A to B. Below the line, the rules for Pi: (Pi I), from Gamma, a proves M of type A, conclude Gamma proves capital lambda a dot M of type Pi a A; (Pi E), from Gamma proves M of type Pi a A, conclude Gamma proves M applied to the type B, of type A with B for a, provided the type variables of B are declared in Gamma.
Figure 5.1. The typing rules of the polymorphic λ-calculus.

The rules for Π are those of dependent types with the domain changed from things to types, and the context is the same stack. There are no pairs, no unions and no ⊥, for the reason the logic has already given: they can be defined.

Erasure and decoration go through as before, so a formula without letters is derivable from no hypotheses exactly when a closed program of the corresponding type exists. (The letters are the logic’s constants, and the calculus has no counterpart for them, just as it has no types like Int built in.)

Data types, defined

Decorate the derivations in the proof of the proposition on the connectives, and the definitions of ∧, ∨ and ⊥ become programs, with a for X and C for the result type:

(M,N):=Λa.λk.kMN(∧I), with k:A→B→a,fstP:=P[A](λh.λk.h)(∧E1),sndP:=P[B](λh.λk.k)(∧E2),LeftM:=Λa.λf.λg.fM(∨I1); RightM has gM,case P of {…}:=P[C](λh.Q)(λk.R)(∨E),absurdP:=P[C](⊥E).

In the line for (∨E) the braces hold the usual two branches, Lefth⇒Q and Rightk⇒R. Each branch becomes a function of its branch variable, λh.Q and λk.R, and the union itself chooses which of the two to call.

They also run as they should:

fst(M,N)=(Λa.λk.kMN)[A](λh.λk.h)⇝(λk.kMN)(λh.λk.h)⇝(λh.λk.h)MN⇝∗M,

and a case on LeftM runs to Q[M/h] in the same way. So pairs, unions and the empty type come free with functions and Π.

The same trick defines the data types you know. The booleans and the natural numbers are

Bool:=Πa(a→a→a),Nat:=Πa((a→a)→a→a),

with

true:=Λa.λh.λk.h,false:=Λa.λh.λk.k,n―:=Λa.λf.λh.f(⋯(fh))the numeral, with n copies of f.

Addition is

add:=λm.λn.Λa.λf.λh.m[a]f(n[a]fh):Nat→Nat→Nat:

do n steps, then m more.

Remark (looking back: booleans and numerals). These are the booleans and numerals of Chapter 1, where

  • true was K,

  • false was KI,

  • n―fx applied f to x n times.

That chapter never gave the numerals a type. In the simply typed calculus each of them has the type (A→A)→A→A for one A chosen in advance, and then exponentiation, which uses one numeral at the type of another, has no type that takes two numerals of the same type to a third. The polymorphic type lets every use choose its own:

exp:=λm.λn.Λa.n[a→a](m[a]):Nat→Nat→Nat

computes mn.

And the two projections that define ∧, λh.λk.h and λh.λk.k, are true and false again.

Existential types, defined

The counter of Example 5.5 had the type Σa(a×(a→a)×(a→Int)). In the polymorphic calculus Σ over types is defined like ∨, by what you can do with it:

ΣaA:=Πb(Πa(A→b)→b),

“whatever follows from A, whatever a is, follows”.

The pattern match in tick3 opens the pair at the result type Int: instantiate b at Int and hand it Λa.λh.N. That substitution puts Int under the Πa, so it is legal only because Int doesn’t mention a, which is the escape condition once more.

5.5 Normalisation and parametricity

Now the question the logic left open. Every program of the calculus is a decorated second-order derivation, impredicative instantiations and all, so if the programs all stop, the old argument goes through.

Theorem 5.7 (normalisation for the polymorphic λ-calculus; on loan). Every typed term of the polymorphic λ-calculus runs to a normal form.

Girard proved it in 1971, by extending Tait’s method of computable terms. It is much harder than the normalisation theorems it extends.4 The shape of closed normal forms is as before, and so is the conclusion: there is no closed term of type Πaa, and second-order logic doesn’t prove ⊥. Impredicativity is safe here.

The same argument says more.

  1. A closed normal term of type Πa(a→a) is Λa.λh.M, with M:a in the context a,h:a.

  2. The type a has no introduction form, so M is neutral (a variable with eliminations applied to it), with head h.

  3. Nothing can be eliminated from h:a, so M is h.

The only program of type Πa(a→a) is the identity. The type says what the program does, without your having to read the program.

Reynolds called this parametricity, and Wadler turned it into a slogan, theorems for free (Wadler, 1989). It is Okafor’s argument, made exact. The same reasoning one type over shows that every closed program of type Πa(a→a→a), the type of pick, runs to Λa.λh.λk.h or to Λa.λh.λk.k, and to nothing else (Exercise 5.3). Whatever its four hundred lines do, pick keeps the first record or keeps the second, always the same one, and hands it back untouched.

The language is also very strong. Every function it can type from Nat to Nat stops on every input, so it has no fix, but the functions it can type are exactly those that second-order arithmetic can prove total, which is far more than any programmer has needed.5

There is a limit. It is the old story of a collection that contains itself. In the polymorphic λ-calculus a type quantifies over types, but the collection it ranges over is not itself a type, so no program can be handed the collection of all types as a value.

Martin-Löf’s first type theory, of 1971, took the last step: a type Type of all types, with Type:Type. Girard showed that it proves everything. There is a closed term of every type, and it never stops, built, like the Monster and Russell’s paradox before it, from a collection that contains itself.6 The repair, in Martin-Löf’s later theories, is a hierarchy of universes, Type0:Type1:Type2:⋯, none of them a member of itself.

5.6 History

System F was found twice, like the quantifier and like natural deduction. Girard found it from the logic’s side in 1971, as the programs of second-order logic, and used it to study second-order arithmetic. Reynolds found it from the programmer’s side in 1974, as the language a generic function is written in, and later gave the reason types like Okafor’s say so much. Neither knew of the other.

The fear behind it is older. Russell banned impredicative definitions in 1908 after the paradoxes, and Principia built its types to keep them out; Church’s simple theory of types of 1940 is that idea made small enough to use. Girard’s normalisation theorem showed that the ban was more than this logic needs, and his paradox of 1972 showed where the real limit lies. Figure 5.2 collects the dates.

WhoWhenWhat
Russell1908impredicative definitions banned; the ramified theory of types
Church1940higher-order logic: the simple theory of types
Girard1971System F; second-order logic decorated; normalisation
Martin-Löf1971a type of all types, Type:Type
Girard1972Type:Type proves everything
Reynolds1974the polymorphic λ-calculus, independently
Reynolds1983parametricity
Wadler1989parametricity as theorems for free
Figure 5.2. Second-order logic and polymorphism: who did what.

Summary

A quantifier can range over propositions as well as things. Then a schema, a claim about every formula made one instance at a time, becomes a single theorem such as ∀X(X→X), and ∧, ∨ and ⊥ needn’t be primitive: each is defined by what you can do with it, from → and ∀ alone.

Decorated, the derivations of this second-order logic are the programs of the polymorphic λ-calculus. A (∀I) bar is a type abstraction and a (∀E) bar an instantiation, and pairs, unions, booleans and numerals are all definable.

The quantifier is impredicative, since it ranges over a collection that contains the propositions it helps to form, so consistency needed a new argument. Girard’s normalisation theorem supplies it, and with it parametricity: a type such as Πa(a→a→a) says what its programs do without anyone reading them. Go one step further, to a type of all types, and the system proves everything.

That is the end of Part 1, and three questions to ask of every proof system.

  1. Could it prove something false? We’ve shown that these logics don’t prove everything, which is all that “false” can mean until the word has a meaning.

  2. Does it prove everything it should? We can’t even ask.

  3. Can the search for a proof be made mechanical, an algorithm rather than a genius? Not yet.

And a puzzle to take into next week. Peirce’s law,

((p→q)→p)→p,

is a formula built from letters and arrows like any other. With type variables in place of the letters it is the type ΠaΠb(((a→b)→a)→a), in Haskell ((a -> b) -> a) -> a. Try to write a program of that type, in Haskell if you like, without recursion and without undefined. You won’t manage, and nothing so far can tell you whether that’s your fault or the formula’s. Part 2 begins by giving the formulas a meaning, and the first thing it finds out is what Peirce’s law is.

Exercises

Exercise 5.1.

  1. Write the types of length and map with their quantifiers explicit.

  2. Type the use map show [1, 2], naming the instantiation at each quantifier.

  3. Declare f :: a -> b and define f x = x: show the exact judgement at which the checker is asked to generalise over a variable that is not on top of the stack, and write the error in the words of the section on generics.

Exercise 5.2. With A∧B and A∨B defined in second-order logic, type the programs (M,N), fst and case that decorate the proof of Proposition 5.3, and run

case (RightM) of {Lefth⇒Q∣Rightk⇒R}

to normal form.

Exercise 5.3. Show that the closed normal terms of type Πa(a→a→a) are exactly true and false. What are the closed normal terms of type Πaa, and what does that say about ⊥?

Exercise 5.4.

  1. Type add and exp on Nat.

  2. Give a program mult:Nat→Nat→Nat.

  3. Run add2―1―[a]fh to normal form.

References

  • Alonzo Church. A formulation of the simple theory of types. Journal of Symbolic Logic, 5(2):56–68, 1940.
  • Jean-Yves Girard. Interprétation fonctionnelle et élimination des coupures de l’arithmétique d’ordre supérieur. Thèse de doctorat d’état, Université Paris VII, 1972.
  • 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.
  • John C. Mitchell and Gordon D. Plotkin. Abstract types have existential type. ACM Transactions on Programming Languages and Systems, 10(3):470–502, 1988.
  • John C. Reynolds. Towards a theory of type structure. In Programming Symposium, Paris, 1974, volume 19 of Lecture Notes in Computer Science, pages 408–425. Springer, 1974.
  • Philip Wadler. Theorems for free! In Proceedings of the Fourth International Conference on Functional Programming Languages and Computer Architecture (FPCA ’89), pages 347–359. ACM, 1989.

Notes

  1. The argument needs a language that keeps its promises: no casts, no reflection, no escape hatches that let code inspect a value of a type it was told nothing about. In Java every object has a toString and can be cast, and the argument fails. Haskell’s escape hatches are named unsafe for this reason, and a review checklist can ban them. ↩

  2. Mitchell and Plotkin (1988) were the first to put it this way; the title of their paper is “Abstract types have existential type”. Girard found the polymorphic λ-calculus in 1971 and called it System F (Girard, 1972); Reynolds found it independently in 1974 (Reynolds, 1974). Its quantifier is the one behind generics in most typed languages. ↩

  3. This is the argument for writing operating-system drivers in a language that enforces the condition. Rust, which does, was accepted into the Linux kernel for exactly this kind of boundary from version 6.1 in 2022. ↩

  4. The method is reducibility candidates: a property of terms is defined for every type, and for ΠaA the definition quantifies over all candidate properties, impredicatively, as the type does. The proof is in Girard et al. (1989, chapter 14). ↩

  5. Girard’s representation theorem (Girard, 1972); Girard et al. (1989, chapter 15). Ackermann’s function, which no program built from bounded loops computes, is easy to write in System F. ↩

  6. This is Girard’s paradox (Girard, 1972), a typed cousin of Russell’s. ↩