[By Jeff Vaughan] Contents I) Recursive Substitution Model A) Easy Examples B) List reverse C) SKK II) Induction A) Weak B) Strong C) Broken D) Structural I) RSM I. A) First a few easy RSM examples. These are all similar, and are intended to illustrate function closures, and to be careful when substituting. A really easy example: (fn(x) => ((fn(y) =>y) 312)) 211 By the rules for anonymous functions we replace unbound occurrences of x with 211 in (fn(y) =>y) 312 There are no unbound occurrences of x so we still have (fn(y) => y) 312 Now we replace unbound occurrences of y with 312 in y Clearly y is unbound so now we have 312 (fn(x) => ((fn(x) =>x) 312) ) 211 By the rules for anonymous functions we replace unbound occurrences of x with 211 in (fn(x) =>x) 312 There are no unbound occurrences of x so we still have (fn(x) => x) 312 Now we replace unbound occurrences of x with 312 in x Clearly x is unbound so now we have 312 (again) (fn(x) => ((fn(y) =>x) 312) ) 211 By the rules for anonymous functions we replace unbound occurrences of x with 211 in (fn(y) =>x) 312 Ah-ha! this time there one (fn(y) => 211) 312 Now we replace unbound occurrences of x with 312 in 211 But there are no free variables, so the answer remains 211 (not nearly as nice an answer) These functions were pretty clear by inspection. Time for one that's a bit trickier: I. B) List reverse fun rev(l:int list) = if l = nil then nil else (rev (tl l))@[hd l] This is bad style. No, abysmal style. But we have rules for functions and not if and not for pattern matching and case. for convenience if will define *body* to be the text "if list = nil then nil else (rev (tl l))@[hd l]" rev [3,1,2] (fun rev(l) = *body*) [3,1,2] Now substitute [3,2,1] for unbound l in the function body and unroll rev one time if [3,1,2] = nil then nil else ((fun rev l = *body*) (tl[3,1,2])) @ [hd [3,1,2]] if false then nil else ((fun rev l = *body*)(tl [3,1,2])) @ [hd [3,1,2]] I'm gonna start kicking the appends off to the right. It's easier to read like that. ((fun rev l = *body*) (tl [3,1,2])) @ [hd [3,1,2]] ((fun rev l = *body*) [1,2]) @ [hd [3,1,2]] Now substitute [2,1] for unbound l in the function body and unroll rev one time (if [1,2] = nil then nil else ( (fun rev l = *body*) (tl [1,2]))@[hd [1,2]]) @ [hd [3,1,2]] (if false then nil else ((fun rev l = *body*)(tl [1,2])) @ [hd [1,2]]) @ [hd [3,1,2]] (((fun rev l = *body*) (tl [1,2])) @ [hd [1,2]]) @ hd [3,1,2]] (((fun rev l = *body*) [2] ) @ [hd [1,2]]) @ [hd [3,1,2]] Just tidying up parens, etc...: ((fun rev l = *body*) [2]) @ [hd [1,2]] @ [hd [3,1,2]] Again: (if [2] = nil then nil else ((fun rev l = *body*) (tl [2])) @ [hd [2]]) @ [hd [1,2]] @ [hd [3,1,2]] (if false then nil else ( (fun rev l = *body*) (tl [2])) @ [hd [2]]) @ [hd [1,2]] @[hd [3,1,2]] (((fun rev l = *body*) (tl [2])) @ [hd [2]]) @[hd [1,2]] @[hd [3,1,2]] ((fun rev l = *body*) (tl [2])) @ [hd [2]] @ [hd [1,2]] @ [hd [3,1,2]] ((fun rev l = *body*) []) @ [hd [2]] @ [hd [1,2]] @ [hd [3,1,2]] Substitute. Unwind. Yehaw. (if [] = nil then nil else ((fun rev l = *body*) (tl [])) @ [hd []]) @ [hd [2]] @ [hd [1,2]] @ [hd [3,1,2]] (if true then nil else ( (fun rev l = *body*) (tl [])) @ [hd []] ) @ [hd [2]] @ [hd [1,2]] @[hd [3,1,2]] ( nil ) @ [hd [2]] @ [hd [1,2]] @ [hd [3,1,2]] We're done with recursion, but our function was not tail recursive. Time to clean the stack: nil @ [hd [2]] @ [hd [1,2]] @ [hd [3,1,2]] I skiped a bunch of reduction steps because they were obvious. This is sometimes ok on homework in exams. It's like cooking, skip steps to taste, but be aware it's dangerous. nil @ [2] @ [1] @ [3] [2,1,3] Done. So this was complicated. Let's do something else complicated: I. C) SKK val S = fn(x) => fn(y) => fn(z) => x z (y z) val K = fn(x) => fn(y) => x Let's evaluate (I'm gonna use [] and {} for grouping. It's not quite legit, but such is life) S K K Function application is left associative so we have (S K) K [[ fn(x) => fn(y) => fn(z) => x z (y z) ] [fn(x) => fn(y) => x]] [fn(x) => fn(y) => x] [ fn(y) => fn(z) => [fn(x) => fn(y) => x] z (y z) ] [fn(x) => fn(y) => x] fn(z) => [fn(x) => fn(y) => x] z ([fn(x) => fn(y) => x] z) note that this give us []z[] Functions applications is left associative so we have fn(z) => {[fn(x) => fn(y) => x] z} ([fn(x) => fn(y) => x] z) fn(z) => { fn(y) => z } ([fn(x) => fn(y) => x] z) As we can't reduce the expression on the left anymore we need to try the one on the right before we can do the application. fn(z) => { fn(y) => z } ( fn(y) => z) ) fn(z) => z Therefore S K K is the identity function If you type this into the interpreter, you'll get a warning, but SKK still works: - S K K; stdIn:1.1-41.3 Warning: type vars not generalized because of value restriction are instantiated to dummy types (X1,X2,...) val it = fn : ?.X1 -> ?.X1 - S K K 3; val it = 3 : int What's going on? Well the short answer is that we'll get to it in a few weeks. Another short answer is that we've bumped into the imperative side of SML. II) Induction There are four parts two and induction proof 1. State what you want to prove 2. State what you will do induction on (often one or "math words," like "positive integers," is appropriate) 3. Prove the base case (e.g. P[0]) 4. Prove the inductive case. (e.g. P[m] => P[m+1]) 4 1/2. State the induction hypothesis 4 3/4. Clearly label where you use the induction hypothesis 4 7/8. State your conclusion II. A) Weak Induction Weak induction is what we are most used to. Here we assume a proposition holds for some fixed m and show that it holds for the m+1 case. Example: Correctness of the factorial function. Ramin didn't finish this in lecture, so I stole the example from his notes. Example: fact(n) = n! We clearly state each the four steps fact(n) = n! Induction on n in Z+ We need to show fact(1) = 1. See below. We need to show that for any m in Z+, fact(m) = m! => fact(m+1) = (m+1)! See below. Now, to make life a little easier we can introduce a shortcut and write BODY (in a box!) instead of if (z = 1) then z else z * fact (z - 1). On homework or on the exams, if you do this, we expect you to clearly state what BODY (in a box!) is. Then we do the base case. eval((fun fact(z) = BODY) (1)) eval((fn (z) => if (z = 1) then z else z * (fun fact(z) = BODY) (z - 1)) (1)) eval(if (1 = 1) then 1 else 1 * (fun fact(z) = BODY)(1 - 1))) 1 Then we will continue proving the inductive step: eval((fun fact(z) = BODY) (m+1)) eval((fn (z) => if (z = 1) then z else z * (fun fact(z) = BODY) (z - 1)) (m+1)) eval(if (m+1 = 1) then m+1 else (m+1) * (fun fact(z) = BODY)(m + 1 - 1))) We know that m element Z+, then m >= 1 and m+1>1, therefore (m+1 !=1) and the if expression will evaluate the else clause. eval((m+1) * (fun fact(z) = BODY) (m)) eval(eval(m+1) * eval((fun fact(z) = BODY)(m))) Now we apply our Induction Hypothesis. eval((m+1) * m!) (m+1)! We have shown that fact(n) = n! II. B) Strong Induction Here we assume that a proposition holds for all n (b,x::a)) ([],[]) fun merge(l: 'a list, l': 'a list) = case (l, l') of (hd::tl, hd'::tl') => if hd l' in case l of [] => [] | [a] => [a] | _ => let val (a,b) = split l in merge (sort a, sort b) end We want to show that for any list, l, sort(l) returns the appropriate sorted list. Proof is by induction on the length of list l. Length l element of Z+0 (integers >= 0). I'm not gonna give a whole proof here. We would need to prove a few lemma's. Lemma 1: if val (x,y) = v(l) and length l > 1 then length x < length l and length y < l Lemma 2: if x and y are sorted then merge(x,y) returns sorted list containing all the elements in x and y The main proof has two bases cases length l = 1 and length l = 0. We need to do this because we don't have a general method to reason about all three cases for l. For the inductive step, we assume that for some fixed number k, if length l < k then then sort(l) works correctly. We need to show that, given the preceding, if length l' = k then sort(l') works correctly too. II. C) Broken Induction Here we assume that a proposition holds for some fixed m then use the assumption to prove itself, or do something else equally nefarious. This can be subtle; consider the following proof attributed to Joel E. Cohen : All horses are the same color Proof by induction. Base case: 1 horse is clearly the same color as itself Assume all sets of n horses are the same color Take a collection of n horses, they must be of the same color. Add another horse and take away a different one. Here we have a different set of n horses all of which are the same color. Thus the n+1 horses must be of the same color. All horses are the same color by induction. * Where is the error here? It's subtle as the base case in correct, and the inductive step is correct for the cases of three or more horses. The problem comes in when we consider two horses. If we have {h1, h2, h3} removing h1 and apply the induction hypothesis to {h2,h3} gives us h2=h3 (where = is the symmetric, transitive, reflexive equine color relation). Removing h3 gives us h1=h3. Applying transitivity we have h1=h2=h3. In the case of {h1,h2} removing h1 gives us h2=h2. And removing h2 yields h1=h1. Only given h1=h1 and h2=h2 we'll have an awfully hard time showing h1=h2. The moral of the story is to be careful. This could be a good place to insert a rant about insufficient mathematical rigor and the downfall of naive set theory. I have a feeling this a probably two sections worth of content. Please let me know I you need more for Wednesday.