• No results found

The natural numbers

In document Type Theory & Functional Programming (Page 113-118)

We have already seen the natural numbers as a base type of ourλ-calculus in chapter 2.

Formation Rule forN N is a type(N F)

Natural numbers are either zero or a successor.

Introduction Rules forN

0 : N(N I1)

n : N

(succ n) : N(N I2)

We eliminate natural numbers by means of definition by primitive re- cursion. Modifying the rule we saw earlier, we have

Elimination Rule forN (Special Case)

n : N c : C f : (N ⇒C⇒C)

prim n c f : C (N E)

If we discharge the assumption thatn : N, then

c : C f : (N ⇒C⇒C)

λnN.(prim n c f) : (N⇒C)

which is the familiar rule for primitive recursion which we saw in section 2.9. Why is the rule we have presented above a special case? To answer this we turn to our logical view of the type.

The proof principle which goes along with primitive recursion is (“math- ematical”) induction. Suppose that we wanted to show that, for example,

4.8. THE NATURAL NUMBERS 101 all the factorials of natural numbers are strictly positive. This assertion takes the form

(∀n:N)(f ac n >0) ≡df (∀n:N)C(n)

What do we have to do to prove this? First of all we show that C(0) is valid, that is we supply somecwith

c : C(0)

and then we show that C(n+ 1) is valid, assuming that C(n) is. In this case we supply some

f0 : “C(n)⇒C(n+ 1)”

In fact, thef0 can be slightly more general,

f : “N ⇒C(n)⇒C(n+ 1)”

Note that we have enclosed the types in inverted commas — they are not part of our system. We can make them so, using the dependent type con- structor:

f : (∀n:N)(C(n)⇒C(succ n)) Given these we produce the proof:

Elimination Rule forN (General Case)

n : N c : C[0/x] f : (∀n:N).(C[n/x]⇒C[succ n/x])

prim n c f : C[n/x] (N E)

Again, if we discharge the assumptionn : N, we have

c : C[0/x] f : (∀n:N).(C[n/x]⇒C[succ n/x])

λnN.(prim n c f) : (∀n:N). C[n/x]

which is the familiar proof of the universal statement.

The computation rule is exactly the same in the two cases. Thinking of a computation of a recursive function we inspect the argument and then

unfold the definition according to whether we are at the base case or not.

Computation Rules forN prim0c f → c

prim(succ n)c f → f n(prim n c f)

What do the rules mean in the logical case? They tell us how to build a proof for any particular natural number that we might supply. This is, of

course, how we justify the rule in the first place. UsingC(k) forC[k/x], we argue thatC(2) is valid thus: “C(0) is valid outright, and by the inductive case forn= 0,C(0)⇒C(1) and applyingmodus ponens, we haveC(1). In a similar way, we haveC(1)⇒C(2), and so we can getC(2).”

This rule is one of the high points of type theory. Intuitively, we can appreciate that there is an affinity between the rules for primitive recursion and mathematical induction. Functions introduced by primitive recursion have their properties proved by mathematical induction. What is so elegant here, with our identification of propositions and types, is that they are

exactly the same rule.

Let us consider some examples. The successor function is defined to be (λx:N)(succ x)

For the purposes of illustration, without recommeding this as an efficient algorithm, we now examine the behaviour of a successor function defined by primitive recursion:

addone0 = 1

addone(n+ 1) = (addone n) + 1 which is formalised thus:

addone≡dfλxN.(prim x(succ0)f)

where

f ≡df λnN.(λyN.(succ y))

What happens when we apply addone to the formal representative of 2, that issucc(succ0)?

((λx:N)(prim x(succ0)f)) (succ(succ0))

→ (prim(succ(succ0)) (succ0)f)

→ f (succ0)(prim(succ0) (succ0)f)

≡ ((λn:N)(λy:y)(succ y)) (succ0) (prim(succ0) (succ0)f)

→ succ(prim(succ0) (succ0)f) By a similar process we see that

prim(succ0) (succ0)f →→ (succ(succ0)) and so finally we see that

4.8. THE NATURAL NUMBERS 103 where ‘→→’ is generated from → `a la definition 2.7. We shall look at the successor function again when we come to look at equality, and in particular equality of functions.

Note that to make the definition above readable, we used the device of naming it,addoneand giving explicit recursion equations for it. This helps us to read these definitions, and it is quite possible for an implementation either to decide whether a particular set of recursion equations constitutes a definition by primitive recursion, or to provide a “pretty printed” version of any primitive recursive definition. We shall continue to give these equa- tional versions of functions defined in this and similar ways. We shall also use ‘(n+ 1)’ instead of ‘(succ n)’ whenever this can cause no confusion.

Primitive recursion is a powerful method of definition. We can define addition thus:

add m0 = m

add m(n+ 1) = succ(add m n) so formally we say

add≡dfλm . λn . prim m(λp . λq .(succ q))n

In a similar way

mult m0 = 0

mult m(n+ 1) = add m(mult m n) which may be rendered formally thus:

mult≡dfλm . λn . prim0 (λp . λq .(add m q))n

There are standard expositions about what can be defined by primitive re- cursion over the natural numbers, a good reference being [Cut81]. Among the functions are: the usual arithmetic operations; bounded search, pro- viding search within a finite range for an object with a property defined by a primitive recursive function withboolean values; definition by course- of-values recursion and so forth. We shall look again at the various forms of recursion available within the system below; see sections 4.9 and 5.10 as well as 6.1.

The functions we see above are first order in that their arguments are numbers. It is well known that there are limits to the expressibility of first order primitive recursion. Ackermann gave a graphic example of this with his ‘fast-growing’ function, which is proved to be non (first order) primitive recursive in [P´67]. The system here is more powerful, since arguments can

be higher order, and here we give a version of the Ackermann function. The two argument version of the function is given by the recursion equations

ack0n = n+ 1

ack(m+ 1) 0 = 1

ack(m+ 1) (n+ 1) = ack m(ack(m+ 1)n)

We can take a higher-order view of this, defining the functions thus:

ack0 = succ

ack(m+ 1) = iter(ack m) Where the functioniter, of type

(N ⇒N)⇒(N⇒N)

iterates its argument, having the definition

iter f 0 = 1

iter f (n+ 1) = f (iter f n) This function is given by the term

λf(N⇒N). λnN. prim n1 (λp . λq .(f q))

which we shall calliter, and the Ackermann function itself will be given by

λnN.(prim n succ λp . λg .(iter g))

There is a limit to the expressibility of primitive recursion, even at higher or- ders. All functions defined by primitive recursion are total, and so there are intuitively computable functions which are not primitive recursive. Among these are the functions which code an interpreter for the process of computa- tion of primitive recursive functions. We return to the issue of expressibility below.

We are not in a position to give any non-trivial examples of proof by induction as we still have not defined any predicates which contain free variables, a situation which we remedy in due course (in section 4.10, in fact).

Exercises

4.21. Define the equality function

4.9. WELL-FOUNDED TYPES — TREES 105

In document Type Theory & Functional Programming (Page 113-118)