Developing arithmetic in Gödel's system T

April 11, 2011

This year I’ve been running a reading group on type theory. Our primary text is Proofs and Types by Jean-Yves Girard, Yves Lafont and Paul Taylor. The focus of the book is the development of the typed λ-calculus, with a strong proof-theoretic slant, these two perspectives being unified by the Curry-Howard correspondence. It begins by exploring the connections between intuitionistic logic and the simply typed lambda calculus, and works up to a fairly in-depth study of System F.

One of the intermediate steps is the system T, originally developed by Kurt Gödel in his Dialectica interpretation, and reformulated by Tait in 1967. I won’t try to cover the history here—there are plenty of articles out there if you’re interested. (The names of the articles alluded to are given in the references at the end.)

The modern formulation of T is as a typed λ-calculus with natural number and boolean types. In chapter 7 of Proofs and Types, Girard shows how the natural numbers can be represented in T, and provides a definition of addition based on the recursion operator R. He goes on to say that

Among easy exercises in this style, one can amuse oneself by defining multiplication, exponenential, predecessor etc.

So, let’s amuse ourselves! Thinking about these simple examples is helpful because it gives a working demonstration of how the recursion operator adds power back to the language which was lost when the restrictive type system was introduced.

For example, the untyped λ-calculus allows one to use Church encoding to represent the natural numbers, but in the simply typed lambda calculus one is barred from forming the relevant terms by the requirement that every term be well-typed.

During the reading group meeting where we discussed this chapter, we managed to assemble a working definition for multiplication, which I’ll present below along with the other arithmetical functions Girard suggests. However, before we get onto that, I’m going to briefly sketch how the type of natural numbers Nat is defined in T; the introduction and elimination rules for terms of that type, on which everything else is built; and how an addition function can be defined in terms of those rules.

A small niggle: Girard actually talks about an integer type Int. When one examines the nature of the type it becomes clear that what’s intended is a natural number type, so I’m going to talk solely about natural numbers and call the type in question Nat, not Int.

Natural numbers in T

System T is a variant of the simply typed lambda calculus, so it has three sorts of rules: those stating which types exist; those stating which terms of those types can be formed; and reduction rules stating how some terms can be reduced to others. These are spelled out in §3.1 of Proofs and Types, so if any of the following doesn’t make sense, I recommend checking that out (the book is freely available from the link above).

For system T, there are also two constant types, Nat and Bool; here we shall only be concerned with Nat so I shall omit the rules mentioning Bool. The additional term formation rules for system T specify how one can introduce and eliminate terms of Nat and Bool. The introduction rules are:

These mirror their equivalents in Peano Arithmetic almost exactly—that is, they represent zero and the successor function. There is also the elimination rule for Nat, which introduces the recursion operator R:

The recursion operator allows us to combine the introduction rules to give recursive definitions of arithmetic operations such as addition and multiplication in a form not too far removed from their counterparts in Peano Arithmetic. Its reduction rule is given by two cases:

The first is the base case, which ensures that the recursion is well-founded. The second rule gives, for the recursive case, a way of eliminating an instance of the recursion operator. The term it reduces to looks more complex than the one we started with, but it’s clear that eventually we’re going to run out of successors and the reduction process will terminate (a proof of this is given in §7.2 of Proofs and Types).

Addition

With the preamble out of the way, we’re finally ready to start defining the usual arithmetic functions within T. The easiest place to start is with the definition of addition.

x+0=x

x+S(y)=S(x+y)

There’s no recursion operator in sight in this definition, but of course there is recursion occurring. The first equation gives the base case, defining the result when the addition function is used to combine a number x with 0. The second equation defines the recursive case, giving the result when we add the successor of some number y to some number x. The recursion operator R has three arguments, and the first two map to these cases: the value of the base case (here it would be x) and the function to apply in the recursive case (here, the successor function).

With this in mind, we can formulate an addition function in T pretty straightforwardly. The variables bound by lambda abstractions have been given type annotations, just to make clear how the types of these terms relate to those specified in the introduction and elimination rules for terms of type Nat. I shall omit these annotations from the definitions in the rest of the article.

add=λxNat.λyNat.Rx(λzNat.λwNat.Sz)y

The second term given to R is just a double lambda abstraction over Sx, representing a two-argument function; the second argument (the numbers accumulating on the right of the reduction) will always be thrown away. The type of the term must be, by the definition we gave earlier, U(NatU). In this case we’re saying that the type U is in fact the constant type Nat, so the term has a type of Nat(NatNat).

I should note that there is no mechanism in T for giving names to definitions (i.e. let binding), so equations like the one given above should simply be seen as expressing abbreviations, not as expressions in the language of T, although the lambda term on the right hand side certainly is.

Multiplication

Again, let’s begin with the definition of multiplication we find in Peano Arithmetic.

x0=0

xS(y)=(xy)+y

Here we can see that in the base case, when we combine 0 with x the result is 0. In contrast, in the base case for addition when we combine 0 with x the result is x. This is reflected in the following definition of multiplication in T, with O as the base case, and addition as the operation applied in the successor case.

mult=λx.λy.RO(λz.λw.(add)xz)y

Expanding the definition of add in mult (with some trivial α-conversion and β-reduction), it’s easy to see the double recursion that we also see in the PA definitions.

mult=λx.λy.RO(λz.λw.Rx(λu.λv.Su)z)y

Exponentiation

Just as multiplication is built on addition, adding an extra layer of recursion, so exponentiation is built on multiplication. The PA equations are

x0=1

xS(y)=xyy

Again, all we need to do to define this new function is state the base case (SO, corresponding to the rule that any number raised to exponent 0 is 1) and the function to apply in the recursive case (multiplication).

exp=λx.λy.RSO(λz.λw.((mult)xz))y

Predecessor

The predecessor function is given by the following equations.

Pred(0)=0

Pred(S(x))=x

Compared to the nested recursions given above, the definition of the predecessor function in T is strikingly simple.

pred=λx.RO(λy.λz.z)x

The insight which allows one to derive it is similarly simple. An application of the predecessor function to some successor (in the case of O the predecessor will simply be O too) will be of this form: RO(λy.λz.z)(Sx). When we look at the reduced term after one reduction, we have something like (λy.λz.z)(RO(λy.λz.z)x)x.

Ignore the new occurrence of the recursion operator; just consider it as another term. Instead, look at the whole formula as an application. Clearly, the first argument to the function on the left will always be discarded: the whole term will always reduce simply to x, the predecessor of Sx, our initial argument.

References & further reading