Reasoned+Programming_Part4 - 46 Functional programming in...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: 46 Functional programming in Miranda Thus entering a function's name without any parameters is the equivalent of entering a value. However, the major di erence between a function value and other values is that two functions may not be tested for equality. This is the case even if they both have precisely the same code or precisely the same mappings for all possible input values. Thus the expression (dd = double) will result in an error. De ning functions In Miranda, new functions are introduced in three steps: 1. Declare the function name and its type (its argument and result types): square :: num -> num 2. Provide the appropriate pre- and post-conditions: ||pre: none ||post: square n = n^2 3. Describe the function using one or more equations: square n = n*n Although type declarations are not mandatory for functions, it is good programming practice to include them with de nitions in all programs. Type declarations act as a design aid since the programmer is forced to consider the nature of the input and output of functions before de ning them. They also document the program and make it more readable since any programmer can immediately see what types of objects are mapped by the function. Of course, the second step is also optional in that the evaluator won't even notice if you miss it out. But we hope by now you are beginning to understand why it is essential. Consider quadratic equations of the form ax2 + bx + c = 0, where x is a variable and a, b and c are constants. Now the solutions for such a quadratic equation are given by p2 b ; 4ac ;b 2a We can de ne a function hasSolutions which given a, b and c returns True or False indicating whether there will be any solution for x: hasSolutions:: num -> num -> num -> bool ||pre: none ||post: hasSolutions a b c iff a*x*x + b*x +c = 0 for some real x hasSolutions a b c = ((a~=0) & (b*b>=4*a*c)) \/ ((a=0) & ((b~=0)\/(c=0))) More constructions 47 This uses the fact that the roots of the quadratic equation are given by the formula above. Note: The speci cation is quite di erent from the de nition, and it takes some mathematical reasoning to relate the two. (a~=0 & b*b>=4*a*c) \/ ((a=0) & ((b~=0) \/ (c=0))), the right-hand side of the de nition, has type bool and its value is exactly the Boolean result you want for the function application. In the above de nition a b c are called the formal parameters. We talk about the left-hand or the right-hand side of an equation or rule. The right-hand side describes how the result is constructed using the parameters. Layout | the o side rule Miranda assumes that the entire right-hand side of an equation lies directly below or to the right of the rst symbol of the right-hand side. This enables the evaluator to spot automatically when the right-hand side of a rule has nished. An advantage of this is that no special character or symbol such as a semi-colon is required to indicate the end of de nition | less typing for the programmer! This is possible because as soon as the evaluator comes across a symbol that violates the o side rule it will take the violation to mean that the right-hand side of the de nition has been completed. On the negative side, however, care must be taken by the programmer to use safe layout. For long de nitions leave a blank line before starting the right-hand side and indent a small standard amount. For example, functionWithALongName = xxxx or functionWithALongName = xxxx Remember that the boundary is set by the rst symbol of the right-hand side and not by the preceding =. 4.4 More constructions Case analysis Often, we want to de ne a function by case analysis. For example, 48 Functional programming in Miranda pdifference :: num -> num -> num ||pre: none ||post: pdifference x y = abs (x-y) pdifference x y = x-y, if x>=y = y-x, if y>x This de nition is a single equation consisting of two expressions, each of which is distinguished by a Boolean-valued expressions called a guard. The rst alternative says that the value of (pdifference x y) is x-y provided that the expression x>=y evaluates to True pdifference is de ned for all numbers since the two guards exhaust all possibilities. In the above the order in which the alternatives are written is not signi cant because the two cases are disjoint (that is, the guards are mutually exclusive), they can't both succeed. However, if cases are not disjoint then the order in which the alternatives are written is signi cant. Thus guards allow us to choose between two or more alternative values of the same type and only one alternative will be selected and evaluated. If there is a possibility of more than one guard evaluating to True, then the alternative selected will be the rst whose guard evaluates to True. Actually, it is good programming practice to write order-independent code, so it is better if guards are mutually exclusive. Also, writing order-independent code aids in the portability of your program: then your program is more like a set of equations. For example, if your guards are mutually exclusive then porting your Miranda program to a parallel machine in which guards may be evaluated simultaneously will not require any alterations to your code. An equivalent de nition for pdifference is pdifference x y = x-y, = y-x, if x >= y otherwise The reserved word otherwise can be regarded as a convenient abbreviation for the condition which returns True when all previous guards return False. Pattern matching on basic types Pattern matching is one of the more powerful features of functional languages. As we shall see in Chapter 6, it is most powerful when used with composite structures such as lists because it lets you delve into the structure. With the basic types it can still be used, though it tends to appear much like case analysis. The idea is that the formal parameters are not just variables, but `patterns' to be matched against the actual parameter. For example, More constructions 49 bitNegate :: num -> num ||pre: x = 0 \/ x = 1 ||post: (x = 0 & b = 1) \/ (x = 1 & b = 0) || where b= bitNegate x bitNegate 0 = 1 bitNegate 1 = 0 Thus pattern matching can be used to select amongst alternative de ning equations of a function based on the format of the actual parameter. This facility has a number of advantages, including enhancing program readability and providing an alternative to the use of guards, which are in exible at times. Furthermore, pattern matching often helps the programmer when considering all possible inputs to a function. For example, it is clear from the above equations that bitNegate is currently only de ned for the values 0 and 1. The notions of disjointedness and exhaustiveness apply to patterns just as for guards similarly, for non-disjoint patterns, it is the rst match that is used. The otherwise guard corresponds to a nal pattern that is simply a variable (and so matches everything). Note that pattern matching and guards can be used together: sign :: ||pre: ||post: || sign 0 sign n num -> num none (n=0 & sign n=0) \/ (n>0 & sign n=1) \/ (n<0 & sign n = -1) =0 = 1, if n>0 = -1, if n<0 Special facilities for pattern matching on natural numbers Patterns can be used to de ne functions which operate on natural numbers (that is, non-negative integers). The operator + is special as it can be used in patterns of the form p+k where p is a pattern and k is a positive integer constant. A number x will match the pattern only if x is an integer and x k. For example, y+1 matches any positive integer, and y gets bound to that integer-minus-one. So, pred :: num -> num ||pre: nat(x) ||post: (pred x = 0 & x = 0) \/ (x > 0 & pred x = x-1) pred 0 = 0 pred (n + 1) = n (nat(x) means that n is a natural number: int(x) ^ x 0.) Notice that patterns can contain variables. This de nition describes a version of the predecessor function. The pattern n+1 can only be `matched' by a value if 50 Functional programming in Miranda matches a natural number forcing pred to be de ned for natural numbers only. Here the patterns are exhaustive and hence cover all natural numbers. Furthermore, we know that the order of equations will not be important in this example since the patterns are disjoint as no natural number can match more than one pattern. n Pre x and in x functions In Miranda, enclosing an in x operator in parentheses converts it to an ordinary pre x function, which can be applied to its arguments like any other function. This can be useful in the context of Chapter 8, where functions are used as arguments of other functions: Miranda 17 (+) 8 9 Miranda False (>) 8 9 Miranda 8 9 $smaller 8 Conversely, user-de ned binary functions can also be applied in an in x form by pre xing their name with the special character $: One simple way of determining whether it is a good idea to have an operator as an in x one is to see if it is associative | (x $f y) $f z = x $f(y $f z) This is because x $f y $f z is then unambiguous. Local de nitions In mathematical descriptions one often nds expressions that are quali ed by a phrase of the form `where : : : '. The same device can also be used in function de nitions. For example, balance*i where i = interestRate/100. In fact, we have already used where in the de nition of fourthroot in Chapter 3. The special reserved word where can be used to introduce local de nitions whose context (or scope) is the expression on the entire right-hand side of the de nition which contains it. For example, f x y = x + a, if x > 10 = x - a, otherwise where a = square (y+1) In any one equation the where clause is written after the last alternative. Summary 51 Its local de nitions govern the whole of the right-hand side of that equation, including the guards, but do not apply to any other equation. Furthermore, following a where there can be any number of de nitions. These de nitions are just like ordinary de nitions and may therefore contain nested wheres or be recursive de nitions. Note that the whole of the where clause must be indented, to show that it is part of the right-hand side of the equation. The evaluator determines the scopes of nested wheres by looking at their indentation levels. In the next example it is clear that the de nition of g is not local to the right-hand side of the de nition of f, but those of y and z are fx =gyz where y = (x+1) * 4 z = (x-1) * x g x z = (x + 1) * (z-1) Let us consider some uses of local de nitions. Firstly, as in fourthroot, they can be used to avoid repeated evaluation. In an expression a subexpression may appear several times, for example z+(smaller x y)*(smaller x y) Here the subexpression (smaller x y) appears twice, and will be evaluated twice, which is rather wasteful. By using a local de nition we can give a name to an expression and then use the name in the same way that we use a formal parameter: z+w*w where w = smaller x y If you like, you can view this use of local de nitions as a mechanism for extending the existing set of formal parameters. Local de nitions can also be used to decompose compound structures or user-de ned data types by providing names for components (as will be seen later, in Chapter 7). It is good programming practice to avoid unnecessary nesting of de nitions. In particular, use local de nitions only if logically necessary. Furthermore, a third level of de nition should be used only very occasionally. Failure to follow these simple programming guidelines will result in de nitions that are di cult to read, understand and reason about. 4.5 Summary Miranda has three primitive data types: numbers, truth-values and characters (num, bool and char respectively). 52 Functional programming in Miranda Miranda also provides many built-in operators and functions. A new function is de ned in three stages. The function's type is declared, the function is speci ed in a comment and then it is de ned using one or more equations. Although type declarations and speci cations are not mandatory for functions, it is good programming practice to include them with all de nitions. Miranda is layout-sensitive in that it assumes that the entire right-hand side of an equation lies directly below or to the right of the rst symbol of the right-hand side (excluding the initial =). This is the o side rule. To aid in the portability of programs try, wherever possible, to write order-independent code. This means writing mutually exclusive guards or patterns. Functions (or other values) can also be de ned locally to a de nition. Such local de nitions can be used to avoid repeated evaluation or to decompose compound structures, as will be seen in Chapter 7. 4.6 Exercises 1. Write de nitions for the functions speci ed in the exercises at the end of Chapter 3. 2. De ne istriple, which returns whether the sum of the squares of two numbers is equal to the square of a third number. A Pythagorean triple is a triple of whole numbers x y and z that satisfy x2 + y2 = z2. The Miranda function istriple should be declared as follows: istriple :: num -> num -> num -> bool ||pre: none ||post: (istriple a b c) <-> a,b,c are the lengths of the || sides of a right angle triangle The function takes as arguments three numbers and returns true if they form such a triple. Evaluate the function on the triples 345 5 12 13 12 14 15 and check that the rst two are Pythagorean triples and the third is not. Do this exercise twice: rst assume that c is the hypotenuse and then rewrite it so that any of the parameters could be the hypotenuse. Chapter 5 Recursion and induction 5.1 Recursion Suppose we want to write a function sum n which gives us the sum of the natural numbers up to n, that is, Pn=0 i: i n = 0 + 1 + 2 + 3 + : : : + (n ; 1) + n Inspecting the above expression we see that if we remove `+n' we obtain an expression which is equivalent to sum(n ; 1), at least if n 1. This suggests that sum n = sum (n ; 1) + n (5.1) We say that the equation exhibits a recurrence relationship. To complete the de nition we must de ne a base case which speci es where the recursion process should end. For sum this is when the argument is 0. Thus the required de nition is sum sum :: ||pre: ||post: sum n num -> num nat(n) sum n = sum(i=0 to n) i = 0, if n = 0 = sum (n-1) + n, if n > 0 `sum(i=0 to n) i' is intended to be a typewriter version of `Pn=0 i'. If we i just used the recurrence relation (5.1), forgetting the base case, then we would obtain non-terminating computations as illustrated in Figure 5.1. Function de nitions, like that of sum, that call themselves are said to be recursive. Obviously, the computation of sum involves repetition of an action. Often when describing a function | such as sum | there are in nitely many cases to consider. In conventional imperative programming languages this is solved by using a loop, but in functional languages there are no explicit looping constructs. Instead, solutions to such problems are expressed 53 54 Recursion and induction sum 3 (sum 2) + 3 (sum 1) + 2 + 3 (sum 0) + 1 + 2 + 3 (sum -1) + 0 + 1 + 2 + 3 : : a black hole Figure 5.1 by de ning a recursive function. Clearly, the recursive call must be in terms of a simpler problem | otherwise the recursion will proceed forever. The example given above illustrated the technique of writing recursive functions, which can be summarised as follows: 1. De ne the base case(s). 2. De ne the recursive case(s): (a) reduce the problem to simpler cases of the same problem, (b) write the code to solve the simpler cases, (c) combine the results to give required answer. 5.2 Evaluation strategy of Miranda We have seen that evaluation is a simple process of substitution and simpli cation, using primitive and user-de ned function de nitions. More precisely, a function application is rewritten (reduced) in two steps. First the actual parameters are substituted for the formal parameters in the de ning equation of the function: this is called instantiation. Then the application is replaced by the instantiated right-hand side expression (see Figure 5.2). During evaluation an expression may contain more than one redex | place where reduction is possible. But in functional languages if an expression has a well-de ned value then the nal result is independent of the reduction route (this is known as the Church-Rosser property). However, an evaluator selects Euclid's algorithm 55 square 4 square n = n*n thus we get: square 4 = 4*4 Figure 5.2 the next reduction (from the set of possible ones) in a consistent way. This is called the evaluator's reduction strategy. We will not discuss reduction strategies here except to mention that Miranda's reduction strategy is called lazy evaluation. Lazy evaluation works as follows: Reduce a particular part only if its result is needed. Therefore, because of lazy evaluation you can write function de nitions such as f n = 1, if n = 0 = n * y, otherwise where y = f(n-1) Although the scope of the local de nition of y is the entire right-hand side of the equation for f, we know that by lazy evaluation y will only be evaluated if it is needed (that is, if and only if the rst guard fails). 5.3 Euclid's algorithm Consider the problem of nding the greatest common divisor, natural numbers: , of two gcd gcd :: num -> num -> num ||pre: nat(x) & nat(y) ||post: nat(z) & z|x & z|y (ie z is a common divisor) || &(A)n:nat(n|x & n|y -> n|z) || (ie any other common divisor divides it) || where z = (gcd x y) We have introduced some notation in the pre- and post-conditions: just means 8, that is, `for all', written in standard keyboard characters. 9 would be (E). Chapter 15 contains more detailed (A) 56 Recursion and induction descriptions of logical symbols. `|' means `divides', or `is a factor of'. (Note that it is not the same symbol as the division sign `/ '.) zjx , 9y : nat: (x = z y) When we write `y : nat', we are using the predicate nat as though it were a Miranda type, though it is not. You can think of `nat(y)' and `y : nat' as meaning exactly the same, namely that y is a natural number. But the type-style notation is particularly useful with quanti ers: 9y : nat: P means 9y: (nat(y) ^ P ) (`there is a natural number y for which P holds') 8y : nat: P means 8y: (nat(y ) ! P ) (`for all natural numbers y P holds') Be sure to understand these, and in particular why it is that 9 goes naturally with ^, and 8 with !. They are patterns that arise very frequently when you are translating from English into logic (see Chapter 15). There is a small unexpected feature. You might expect the post-condition to say that any other common divisor is less than z, rather than dividing it: in other words that z is indeed the greatest common divisor. There is just a single case where this makes a di erence, namely when x and y are both 0. All numbers divide 0, so amongst the common divisors of x and y there is no greatest one. The speci cation as given has the e ect of specifying gcd 0 0 = 0 Proposition 5.1 For any two natural numbers x and y, there is at most one z satisfying the speci cation for (gcd x y). Proof Let z1 and z2 be two values satisfying the speci cation for (gcd x y) we must show that they are equal. All common divisors of x and y divide z2, so, in particular, z1 does. Similarly, z2 divides z1. Hence for some positive natural numbers p and q, we have z1 = z2 p z2 = z1 q, so z1 = z1 p q It follows that either z1 = 0, in which case also z2 = 0, or p q = 1, in which case p = q = 1. In either case, z1 = z2. 2 Note that we have not actually proved that there is any value z satisfying the speci cation only that there cannot be more than one. But we shall soon have an implementation showing how to nd a suitable z, so then we shall know that there is exactly one possible result. Euclid's algorithm relies on the following fact. Proposition 5.2 Let x and y be natural numbers, y 6= 0. Then the common divisors of x and y are the same as those of y and (x mod y). Recursion variants 57 Proof For natural numbers x and y there are two fundamental properties of integer division, which in fact are enough to specify it uniquely: if y 6= 0 (pre-condition), then (post-condition) x = y (x div y) + (x mod y) 0 (x mod y) < y Suppose n is a common divisor of y and (x mod y). That is, there is a p such that y = n p and a q such that (x mod y) = n q. Then x = y (x div y) + (x mod y) = n (p (x div y) + q) so n also divides x. Hence every common divisor of y and (x mod y) is also a common divisor of x and y. The converse is also true, by a similar proof. 2 It follows that, provided y 6= 0 (gcd x y) must equal (gcd y (x mod y)). (Exercise: show this.) On the other hand, (gcd x 0) must be x. This is because x j x and x j 0, and any common divisor of x and 0 obviously divides x, so x satis es the speci cation for (gcd x 0). We can therefore write the following function de nition: gcd x y = x, if y=0 = gcd y (x mod y), otherwise Question: does this de nition satisfy the speci cation? Let us follow through the techniques that we discussed in Chapter 3. Let x and y be natural numbers, and let z = (gcd x y). We must show that z has the properties given by the post-condition, and there are two cases corresponding to the two clauses in the de nition: y = 0 : z = x We have already noted that this satis es the speci cation. y 6= 0 : z = (gcd y (x mod y)) What we have seen shows that provided that z satis es the speci cation for (gcd y (x mod y)), then it also satis es the speci cation for (gcd x y), as required. 2 But how do we know that the recursive call gives the right answer? How do we know that it gives any answer at all? (Conceivably, the recursion might never bottom out.) Apparently, we are having to assume that gcd satis es its speci cation in order to prove that it satis es its speci cation. 5.4 Recursion variants The answer is that we are allowed to assume it! But there is a catch. This apparently miraculous circular reasoning must be justi ed, and the key is to notice that the recursive call uses simpler arguments: the pair of arguments y with x mod y is `simpler' than the pair x with y, in the sense that the second argument is smaller: x mod y < y. 58 Recursion and induction As we go down the recursion, the second argument, always a natural number, becomes smaller and smaller, but never negative. This cannot go on for ever, so the recursion must eventually terminate. This at least proves termination, but it also justi es the circular reasoning. For suppose that gcd does not always work correctly. What might be the smallest bad y for which gcd x y may go wrong (for some x)? Not 0 | gcd x 0 always works correctly. Suppose Y is the smallest bad y, and gcd X Y goes wrong. Then Y > 0, so gcd X Y = gcd Y (X mod Y ) But X mod Y is good (since X mod Y < Y ), so the recursive call works correctly, so (we have already reasoned) gcd X Y does also | a contradiction. We call the value y in gcd x y a recursion variant for our de nition of gcd. It is a rough measure of the depth of recursion needed, and always decreases in the recursive calls. Let us now state this as a reasoning principle: In proving that a recursive function satis es its speci cation, you are allowed to assume that the recursive calls work correctly | provided that you can de ne a recursion variant for the function. A recursion variant for a function must obey the following rules: It is calculated from the arguments of the function. It is a natural number (at least when the pre-conditions of the function hold). For instance, in gcd the recursion variant is y. It is calculated (trivially) from the function's arguments (x and y). It always decreases in the recursive calls. For the recursive call gcd y (x mod y ), the recursion variant x mod y is less than y , the variant for gcd x y. Though these rules may look complicated when stated in the abstract like this, the underlying intuitions are very basic. Although we did not mention this explicitly when deriving gcd, the driving force behind recursive de nitions is usually to reduce the computation to simpler cases. If you can quantify this notion of simplicity, nd an approximate numerical measure for it, then that is probably the basic idea for your recursion invariant. Another example | multiplication without multiplying Some processor chips can add and subtract, but do not have hardware instructions to multiply or divide. These operations have to be programmed. Here, in Miranda, is one method for doing this. It uses multiplication and integer division by 2, but these are easy in binary arithmetic. Recursion variants 59 A similar method can be used for exponentiation | computing xn by using (Exercise 5): xn div 2 mult :: num -> num -> num ||pre: nat(n) ||post: mult x n = x*n ||recursion variant = n mult x n = 0, if n=0 = y, if n>0 & n mod 2=0 = y+x, otherwise where y=2*(mult x(n div 2)) The recursion variant is n. The recursive call, used to calculate y, has variant n div 2. It is used when y is used, that is, the second and third alternatives, and in both of these we have n > 0 and so n div 2 < n | the variant has decreased. Proposition 5.3 mult satis es its speci cation Proof There are three cases, corresponding to the three alternatives in the de nition: n = 0: mult x n = 0 = x n. n > 0 n even: mult x n= 2 =2 =x n > 0 n odd: mult x n= 2 =2 =x (mult x(n=2)) x (n=2) n (mult x((n ; 1)=2)) + x x ((n ; 1)=2) + x (n ; 1) + x = x n 2 More general properties of functions The reasoning principle stated above concerned a particular property of a function, namely whether it satis ed its speci cation. But actually, the argument applied to any property of the function that you are interested in proving: as long as you have a recursion variant, then you can reason circularly by assuming that the property holds for recursive calls. For example, consider the sum function of Section 5.1. The recursion variant in sum n is easy | it is just n itself. Having found a recursion variant, we can now prove the properties of sum, such as the following well-known equation: Proposition 5.4 8n: (sum n = 1 n(n + 1)) 2 60 Recursion and induction Proof In the non-recursive case, n = 0, this is obvious: both sides of the equation evaluate to 0. In the recursive case we have sum n= sum(n ; 1) + n = 1 (n ; 1)((n ; 1) + 1) + n because we assume the equation holds 2 for the recursive call 1 n(n + 1) =2 by a little algebra. 2 5.5 Mathematical induction The reasoning principle given in the preceding section was really a packaged form of mathematical induction. There are two basic forms of induction and they are equivalent to each other (see Exercise 7): simple induction and course of values induction. Both should be familiar from school mathematics, but let us review them here. Both are used for proving properties of the natural numbers, that is, non-negative whole numbers, and both have the same underlying idea. You give a general method that shows how you can prove a property for the natural numbers one by one, starting at 0 and working up. Simple induction The ingredients of a simple induction proof are as follows: a predicate P or property on the natural numbers for which you wish to prove 8n : nat: P (n) (P holds for all natural numbers n) the base case: a proof of P (0) the induction step: a proof of 8n : nat: (P (n) ! P (n + 1)), in other words a general method that shows for all natural numbers n how, if you had a proof of P (n) (the induction hypothesis), you could prove P (n + 1). Given these, you can indeed deduce 8n : nat: P (n). This is the Principle of Mathematical Induction. The separate parts can be put in the box proof format, as can be seen in Figure 5.3. If you were using ordinary `forall-arrow-introduction', as in Chapter 17, you would produce a box proof such as that given in Figure 5.4. You could then consider two cases, M = 0 and M = N + 1 for some N , and so you end up more or less as in induction, proving P (0) and P (N + 1). However, in induction, you have a free gift, the induction hypothesis P (N ), as an extra assumption. Without it, the proof would be di cult or even impossible. Mathematical induction 61 . . . N : nat P (N ) P (0) 8n : nat: base case . . . induction step P (N + 1) P (n) simple induction Figure 5.3 Box proof for simple induction M : nat . . . P (M ) 8n : nat: P (n) 8I Figure 5.4 To show how this works, suppose, for instance, you want to prove P (39976). The ingredients of the induction show that you can rst prove P (0) from this you can obtain a proof of P (1) from this a proof of P (2) and so on up to P (39976). Of course, you never need to go through all these steps. It is su cient to know that it can be done, and then you know that P does hold for 39976. Another way of justifying the induction principle is by contradiction: if 8n : nat: P (n) is false, then there is a smallest n for which P (n) is false. What is n? Certainly not 0, for you have proved the base case. So taking N = n ; 1, which is still a natural number, we have P (N ) because n was the smallest counter-example. But now the induction step shows how to prove P (N + 1), that is, P (n), a contradiction. The following is a simple example. Proposition 5.5 For all n, n X i=0 i2 = n (n + 1)(2n + 1) 6 Proof Let P (n) be the above equation, considered as a property of n. We prove 8n : nat: P (n) by simple induction. base case: n = 0 and both sides of the equation are 0. 62 Recursion and induction induction step: Suppose that P holds for N then in the equation for N + 1, LHS = PN +1 i2 i=0 PN 2 = i=0 i + (N + 1)2 = N (N + 1)(2N + 1) + (N + 1)2 by the induct. hyp. 6 N +1 (N + 2)(2N + 3) =6 = RHS 2 Course of values induction Think of how P (39976) was to be proved under simple induction: you work up to P (39975), and then use the induction step. But in working up to P (39975), you actually proved P for all natural numbers less than 39976, and it might be helpful in the induction step to use this additional information. This idea leads to a revised, course of values induction step (with n playing the role of what before was n + 1): a general proof that shows how, if you already know that P holds for all m < n, you can show that P also holds for n. In logical notation, 8n : nat: (8m : nat: (m < n ! P (m)) ! P (n)) Curiously enough, this also replaces the base case. When you put n = 0, the induction step says if you know P (m) for all m < 0 then you can deduce P (0) but there are no m < 0 (remember that we are dealing with natural numbers), so of course you know P (m) for all m < 0. When proving the induction step, the e ect is that for n = 0 there is no special assumption that can be used and P (0) has to be proved just as before. The Principle of Course of Values Induction says that if you prove the course of values induction step, then you can deduce 8n : nat: P (n). In box proof form, a course of values induction proof has the form seen in Figure 5.5. The following is an example. Proposition 5.6 Every positive natural number is a product of primes. (Recall that n is prime i it cannot be written as p q unless either p = 1 q = n, or the other way round.) Proof Let P (n) be the property `n is a product of primes' for positive natural numbers n. Let n be a positive natural number, and suppose (course of values induction hypothesis) that every m < n is a product of primes. We show that n is, too. Double induction | Euclid's algorithm without division 63 N : nat 8m : nat: . . . (m < N ! P (m)) P (N ) 8n : nat: P (n) induction hypothesis course of values induction Figure 5.5 If n is itself prime, then we are done. (This also deals with the special case n = 1 for which there are no positive natural numbers < n.) If n is not prime, then we can write n = p q for some natural numbers p and q, neither of them equal to 1. Then p and q are both less than n, so by induction each is a product of primes. Hence n is, too. 2 We have actually cheated here in order to illustrate the technique in an uncomplicated way. The proof does not illustrate course of values induction on the natural numbers, but a similar principle on the positive natural numbers. The correct proof proves the property P (n) de ned by P (n) def (n > 0 ! n is a product of primes) = Then there are two cases. If n = 0, then P (n) is trivially true (`false ! anything' is always true). Otherwise, n > 0, when we use the proof as given. When we reach n = p q, p and q must both be positive, so that from P (p) and P (q) we deduce that p and q are both products of primes. 2 This example shows a common feature of course of values induction. It proves P for n by reducing to simpler cases (p and q, both smaller than n), which we assume have already been done. 5.6 Double induction | Euclid's algorithm without division Consider the problem of nding the greatest common divisor again but this time replace the division in Euclid's algorithm by repeated subtraction: gcd x y = gcd y x, if x<y = x, if y=0 = gcd (x-y) y, otherwise y is no longer a recursion variant, because in the third clause y does not decrease: x does instead. It is still possible to concoct a recursion variant in this case, namely, r(x y) = 2 (x + y), if x y 64 Recursion and induction = 2 (x + y) + 1, if x < y However, this is somewhat arti cial. The reasoning is that our notion of simplicity is not based simply on a numerical measure, but on the idea of lexicographic order: (x y ) is simpler than (x y) i y < y or y = y and x < x 0 0 0 0 0 You could say that y is almost a recursion variant, certainly it never increases in recursive calls (unlike x). But in the case where y remains unchanged as a variant, it must be helped by x decreasing. There is a quite general principle of well-founded induction (see Appendix A) that uses this idea, but, rather than going into the generalities, here we shall show how to use a double induction. Proposition 5.7 This de nition of gcd satis es the speci cation. Proof We use course of values induction to prove 8y : nat: P (y), where P (y) def 8x : nat: ((gcd x y) terminates and satis es its post-condition) = Therefore let us take a natural number Y , and assume that P (y) holds for all y < Y . Having xed our Y , we now use course of values induction again to prove P (Y ), that is, 8x : nat: Q(x), where Q(x) def (gcd x Y ) terminates and satis es the post-condition. = Therefore, let us now take a natural number X , and assume that Q(x) holds for all x < X . We prove Q(X ). There are three cases, as follows, for the three alternatives in the de nition of gcd: X < Y : gcd X Y = gcd Y X . By the induction hypothesis for y, P (X ) holds, so (gcd Y X ) terminates and satis es its post-condition. But the result z in the post-condition for (gcd Y X ) is also good for (gcd X Y ), so that is OK. X Y and Y = 0: (gcd X Y ) terminates immediately with value X , and we have argued before that X is the greatest common divisor for X and 0. X Y and Y > 0: (gcd X Y ) = (gcd (X ; Y ) Y ): X ; Y is a natural number less than X (because Y > 0), so by the induction hypothesis on x we know Q(X ; Y ). Hence (gcd (X ; Y ) Y ) terminates giving the greatest common divisor for (X ; Y ) and Y , and this is also the greatest common divisor for X and Y since X and Y have the same common divisors as do (X ; Y ) and Y . By induction on x, we now know 8x : nat: Q(x), that is, P (Y ). Hence by induction on y we have 8y : nat: P (y), as required. 2 Summary 65 5.7 Summary A recursive function is a function which calls itself. Functions that require the consideration of a very large number of cases (possibly in nitely many) are typically de ned as recursive functions. Generally, a recursive function de nition has a base case which speci es where the recursion process should end. When you write a recursive de nition, also de ne a recursion variant for it. The existence of a recursion variant proves termination and allows you to reason inductively about the function. The circular reasoning is justi ed by mathematical induction. Simple induction in box proof form. . . . N : nat P (N ) induction hypothesis . . P (0) base case . P (N + 1) induction step 8n : nat: P (n) simple induction Course-of-values induction N : nat 8m : nat: (m < N ! P (m)) induction hypothesis . . . P (N ) induction step 8n : nat: P (n) course of values induction You usually hide the induction by using the `circular' reasoning principle for recursive de nitions (once you obtain the recursion variant). Sometimes you need to make the induction explicit, for example, in double induction. Miranda's reduction strategy is called lazy evaluation. In lazy evaluation the evaluator evaluates an expression only if its result is needed. 5.8 Exercises 1. The factorial of a non-negative integer n is denoted as n! and de ned as: n def (n ; 1) (n ; 2) (n ; 3) : : : 2 1 = 0! is de ned to be 1. Write a function factorial to de ne the factorial of a non-negative integer. Ignore the possibility of integer over ow. factorial ...
View Full Document

This document was uploaded on 08/10/2011.

Ask a homework question - tutors are online