CS 541 Lecture -*- Outline -*- * data-driven recursion ** inductive definition of lists generated inductively by [] and :: def is: [] is an 'a list if x is of type 'a and lst is of type 'a list, then (x :: lst) is an 'a list ** flat recursion on structure of a list; 2 cases: [] and (x::lst). because of the way lists are defined all lists generated from basis: [] inductive step: x ::lst *** length develop this: base case: length [] = 0 inductive case: want length 7::[8,9] = 3 given length [8,9] = 2 Q: how do we get 3 from 2? generalizing from this example, length (x::lst) = 1 + (length lst) -------- fun length [] = 0 | length (x::lst) = 1 + (length lst); --------- type of this is 'a list -> int *** append --------------- FOR YOU TO DO Write a function append, so that: append([1,2,3], [4,5]) = [1,2,3,4,5] append([], [7,8]) = [7,8] --------------- have them develop this do induction on the first argument. Why? because can put stuff on front easily with :: Q: what is the base case? Q: take the above example for the inductive case. what do we want? what are we given? how do you get that? Q: so what are the equations? equations: append([],y) = y append(x::lst, y) = x::(append(lst,y)) fun append([],y) = y | append((x::lst),y) = x::(append(lst,y)); (No good way to do append tail-recursively) Note: there is a built-in append, written @ in SML. *** list equality Q: can you develop the function equal for lists? defined by dual induction equations: equal([],[]) = true equal([],(y::lst2)) = false equal((x::lst),[]) = false equal((x :: lst),(y :: lst2)) = ((x = y) andalso equal(lst,lst2)) fun equal([],[]) = true | equal([],(y::lst2)) = false | equal((x::lst),[]) = false | equal((x :: lst),(y :: lst2)) => ((x = y) andalso equal(lst,lst2)); note: same as the built-in operator = on lists. note: can't compare lists of different types: (like lst and lst2) ** tail recursion: no pending computation on recursive calls. *** length recall equations: length [] = 0 length x::lst = 1 + (length lst) trace this with equations length [5,7,9] = 1 + (length [7,9]) = 1 + (1 + (length [9])) = 1 + (1 + (1 + (length []))) = 1 + (1 + (1 + (0))) = 1 + 1 + 1 = 1 + 2 = 3 this is a picture of the run-time stack. can do this with only constant space by using an auxillary argument i.e, if want something like a variable, make it an argument to an auxilliary function return aux variable when would exit the loop assign it an inital value by passing it along assign all variables simultaneously by recursive call look: it's our friend the while loop! transform these as follows length x = length_iter(x,0) length_iter([],count) = count length_iter(x::lst,count) = length_iter(lst,1+count) *show equational execution of (length [1,2,3]) length [1,2,3] = length_iter([1,2,3], 0) = length_iter([2,3], 1) = length_iter([3], 2) = length_iter([], 3) = 3 call this the *sequence of iterates* The key to designing a tail recursion is designing this sequence... Tail-recursive function can be interpreted using only constant amount of space. --------- fun length x = length_iter(x,0); fun length_iter([],count) = count | length_iter(x::lst,count) = length_iter(lst,1+count); (* using let to make local def *) fun length x = let fun length_iter ([],count) = count | length_iter ((x::lst),count) = length_iter(lst, 1+count) in length_iter(x, 0) end; --------- *** reverse reverse [] = [] reverse x :: lst = (reverse lst) @ (x :: []) @ is the built-in append for lists --------- fun reverse [] = [] | reverse (x::lst) = (reverse lst) @ (x::[]); ---------- Q: can you write this using tail recursion? to make a tail-recursive version, postulate the iterator reverse x = reverse_iter(x,y) what's the right value for y? will want to build up the answer in the extra variable so that reverse_iter([],y) = y so to figure value of y, use reverse [] = [] = reverse_iter([],y) = y so y = [] so now the definition is reverse x = reverse_iter(x,[]) reverse_iter([],y) = y What is the inductive case? That is, what should be the value of reverse_iter(x::lst,y)? Key is to design the sequence of iterates: reverse([1,2,3]) = reverse_iter([1,2,3], []) = reverse_iter([2,3], [1]) = reverse_iter([3], [2,1]) = reverse_iter([], [3,2,1]) = [3,2,1] Q: so what is reverse_iter(x::lst,y)? reverse_iter(lst,x::y) fun reverse_tail_recursive x = let fun reverse_iter([],y) = y | reverse_iter(x::lst,y) = reverse_iter(lst,x::y) in reverse_iter(x,[]) end; *** correctness arguments (can skip) Theorem: for all z: 'a list, reverse_tail_recursive(z) = reverse(z). Proof: We prove (*) reverse_iter(x::lst,y) = (reverse lst) @ (x :: y), from whence correctness follows by induction. basis: reverse_tail_recursive([]) = reverse_iter([],[]) = [] = reverse([]) inductive step: assume reverse_tail_recursive(lst) = reverse(lst) reverse_tail_recursive(x::lst) = reverse_iter(x::lst,[]) = (reverse lst) @ (x::[]) = reverse(x::lst) (You invent (*) by trying to do the inductive proof first.) Proof of (*): (by induction on lst) [basis] reverse_iter(x::[],y) = reverse_iter([],(x::y)) = (x::y) = [] @ (x::y) = (reverse []) @ (x :: y) [inductive case] assume inductively that (*) holds for lst, prove for z::lst. reverse_iter(x::(z::lst),y) = reverse_iter(z::lst,x::y) = (reverse lst) @ (z::(x::y)) = (reverse (z::lst)) @ (x :: y), Comment: this is a bludgeoning kind of proof, but that's good. It means you can essentially calculate it without thinking too much. ** structure of data determines cases. *** recursion over non-empty lists a non-empty list is either [x], or y::lst, where lst is a non-empty list alternatively either x::[] or y::x::lst where lst is a list **** maximum of a list of integers type is: int list -> int Q: what are the cases? Can you write the equations? equations: maxl x::[] = x maxl y::x::lst = max(y,maxl(x::lst)) the function should now be easy... **** nth element of a non-empty list (counting from 0) type is: nth: 'a list * int -> 'a examples: nth([0,1,2], 1) = 1 nth(["a","b","c","d"], 3) = "d" Q: can you write this? equations: nth (x::lst, 0) = x nth (x::lst, n) = nth(n-1, lst) if n > 0 and n <= length(lst) -------------- fun nth(x::lst, 0) = x | nth(x::lst, n) = nth(lst, n-1); (* requires: the n is non-negative and lst has at least n elements *) -------------- *** recursion over natural numbers is similar Q: what is the inductive def of the naturals? Can you write factorial? **** see examples from page 45 on