;; CS101, Spring 2013 ;; Lecture #7 ;; ;; There is a way to avoid leaving a trail of suspended ;; function calls, and that involves using a technique ;; called TAIL-RECURSION and an added parameter called an ;; ACCUMULATOR. ;; The factorial function (from lecture 6): ; CONTRACT: (facty non-neg-whole-num) -> pos-whole-number ; HEADER: (define facty (lambda (n) ...)) ; PURPOSE: compute the factorial of num ; PRE-FUNCTION TESTS: (check-expect (facty 0) 1) (check-expect (facty 1) 1) (check-expect (facty 2) 2) (check-expect (facty 3) 6) (check-expect (facty 4) 24) (check-expect (facty 5) 120) ; FUNCTION: (define facty (lambda (n) (cond ;; base case, n = 0 or 1 [(<= n 1) 1] ;; always return 1 for base case ;; recursive case, n! = n * (n-1)! ;; innermost computation always involves multiplying by 1 [else (* n (facty (sub1 n)))]))) ; POST-FUNCTION PRINTFs: (printf "(facty 0) => ~a~%" (facty 0)) (printf "(facty 3) => ~a~%" (facty 3)) (printf "(facty 6) => ~a~%" (facty 6)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Write a tail recursive version of the factorial function ;; using an accumulator. ; CONTRACT: (facty-acc non-neg-whole-num non-neg-whole-num) -> ; non-neg-whole-num ; HEADER: (define facty-acc (lambda (y acc) ...)) ; 2-parameters ; PURPOSE: Compute y! with an initial acc value of 1. ; NOTE: THE INITIAL VALUE OF THE ACCUMULATOR SHOULD BE EQUAL TO WHAT IS ; RETURNED IN THE BASE CASE OF THE REGULAR RECURSIVE FUNCTION FACTY. ; PRE-FUNCTION TESTS: (check-expect (facty-acc 5 1) 120) (check-expect (facty-acc 0 1) 1) (check-expect (facty-acc 1 1) 1) ; FUNCTION: (define facty-acc (lambda (y acc) (if (<= y 1) ;; base case, return acc acc ;; recursive case, pass two numbers into facty-acc (facty-acc (sub1 y) (* y acc))))) ; POST-FUNCTION DISPLAYS: (printf "(facty-acc 1 1) => ~a~%" (facty-acc 1 1)) (printf "(facty-acc 5 1) => ~a~%" (facty-acc 5 1)) ; ; Tail-recursive functions and accumulators: ; ; Pages 59-61 in Part 4 of the pdf lecture notes. ; ; For functions like facty and sum, the evaluation of ; each recursive function call is suspended, pending ; evaluation of all subsequent function calls. For very ; large input values, these suspended local environments ; can take up too much memory, in which case an error occurs. ; The suspended calls often make the function very slow. ; ; However, if the body of the recursive function is defined ; so that the recursive call contains all the information ; computed INCLUDED IN each call, less memory is needed. ; ; An accumulator function can re-use the same local environ- ; ment for every call...writing over the last value by ; re-using the same part of memory. ; ; The term "tail-recursive" refers to the fact that the ; return value in the recursive case is entirely contained ; within a call the function makes to itself. ; ;; To write functions like the factorial function tail- ;; recursively, we need to add an extra parameter called ;; an ACCUMULATOR to the function definition. ; ; Accumulator functions: ; ; An ACCUMULATOR is an extra input parameter used to ; contain the result of an incremental computation. ; ; Accumulators allow us to write recursive functions like ; facty in tail-recursive fashion (facty-acc). We only ; need to introduce an additional parameter to the function ; definition to contain the accumulated value. ; ; Differences between an accumulator-style function and ; its non-accumulator counterpart: ; ; 1. The base case return value is passed into the ; function as the inital value of the accumulator. ; ; 2. The value returned in the base case clause is the ; current state of the accumulator. ; ; 3. The recursive case performs its operation on the ; accumulator value and passes it as an argument ; to the function. ; ;; summation function using regular recursion (from lecture 6): ; CONTRACT: (sum non-neg-integer) -> non-neg-integer ; HEADER: (define sum (lambda (x) ...)) ; PURPOSE: Sum the natural numbers from 0 to x. ; PRE-FUNCTION TESTS: (check-expect (sum 5) 15) (check-expect (sum 0) 0) (check-expect (sum 1) 1) (check-expect (sum 10) 55) ;;FUNCTION: (define sum (lambda (x) (cond ; base case, return 0 [(= x 0) 0] ; recursive case, add n to recursive call on n-1 [else (+ x (sum (sub1 x)))]))) ; ;; POST-FUNCTION PRINTFs: (printf "(sum 5) => ~a~%" (sum 5)) (printf "(sum 6) => ~a~%" (sum 6)) (printf "(sum 10) => ~a~%" (sum 10)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Write an accumulator version of the summation function. ; CONTRACT: (sum-acc non-neg-int non-neg-int) -> non-neg-int ; HEADER: (define sum-acc (lambda (num acc) ... )) ; PURPOSE: Compute the sum of the numbers from 0 to num. ; NOTE THAT IN THE BASE CASE OF THE SUM FUNCTION, THE RETURN VALUE IS ; 0. THAT VALUE WILL BE THE STARTING VALUE OF THE ACCUMULATOR ; PRE-FUNCTION TESTS: (check-expect (sum-acc 5 0) 15) (check-expect (sum-acc 0 0) 0) (check-expect (sum-acc 1 0) 1) ; FUNCTION: (define sum-acc (lambda (num acc) (cond ; base case; answer is in the accumulator [(= num 0) acc] ; recursive case; add num to acc and make recursive call on num-1 [else (sum-acc (sub1 num) (+ num acc))]))) ; POST-FUNCTION TESTS: (printf "(sum-acc 5 0) => ~a~%" (sum-acc 5 0)) (printf "(sum-acc 8 0) => ~a~%" (sum-acc 8 0)) (printf "(sum-acc 2 0) => ~a~%" (sum-acc 2 0)) ; ; WRAPPER FUNCTIONS: ; ; Wrapper functions let us avoid giving the initial value of the ; accumulator as an argument by calling the accumulator function ; from within another function. ; ; A wrapper function contains only a call to an accumulator function ; using the base case (stopping) value as an argument. ; ;EXAMPLE WRAPPER FUNCTION FOR THE SUM-ACC FUNCTION GIVEN ABOVE: ; CONTRACT: (sum-wrap nat-num) -> nat-num ; HEADER: (define sum-wrap (lambda (num) ... )) ; PURPOSE: Compute the sum of the numbers from 0 to num. ; PRE-FUNCTION TESTS: (check-expect (sum-wrap 5) 15) (check-expect (sum-wrap 0) 0) (check-expect (sum-wrap 1) 1) (check-expect (sum-wrap 10) 55) ; FUNCTION DEFINITION: (define sum-wrap (lambda (num) (sum-acc num 0))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Example: Writing a function using 1) plain recursion, 2) an accumulator, ;; and 3) a wrapper function along with an accumulator: ;; Write a regular recursive function COPY that consumes a string (str) ;; and a number (n) and produces a string that is the result of appending ;; the input string, followed by a space, n times. NOTE: THIS VERSION IS ;; SLIGHTLY DIFFERENT THAN THE ONE WE WROTE IN CLASS (NO SPACES BETWEEN ;; COPIES OF STR). ;; Contract: (copy string non-negative-integer) -> string ;; Header: (define copy (lambda (str num) ...)) ;; Purpose: produce string with num copies of str. ;; Pre-function tests: (check-expect (copy "la" 4) "lalalala") (check-expect (copy "la" 1) "la") (check-expect (copy "do" 2) "dodo") ;; Function: (define copy (lambda (str n) (if (= n 0) ;; if n is 0, return the empty string "" ;; if n > 0, append another copy of str onto return expression (string-append str (copy str (sub1 n)))))) ;; Post-function printfs: (printf "(copy \"me\" 8) => ~a~%" (copy "me" 8)) (printf "(copy \"joy\" 3) => ~a~%" (copy "joy" 3)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Write a recursive function COPY-ACC that consumes a string (str), ;; a number (n), and a base value and produces the result of appending ;; the string to itself n times. ;; Contract: (copy-acc string non-negative-integer string) -> string ;; Header: (define copy-acc (lambda (str n acc) ...)) ;; Purpose: produce string consisting of n copies of str. ; NOTE: In this function, the accumulator should be the value return in the ; base case of copy. ;; Pre-function tests: (check-expect (copy-acc "la" 4 "") "lalalala") (check-expect (copy-acc "la" 1 "") "la") (check-expect (copy-acc "do" 2 "") "dodo") (check-expect (copy-acc "do" 0 "") "") ;; Function: (define copy-acc (lambda (str n acc) (if (= n 0) ;; return the value in the accumulator acc ;; make a tail-recursive call to copy-acc (copy-acc str (sub1 n) (string-append str acc))))) ;; Post-function tests: (printf "(copy-acc \"me\" 5 \"\") => ~a~%" (copy-acc "me" 8 "")) (printf "(copy-acc \"ho\" 0 \"\") => ~a~%" (copy-acc "ho" 0 "")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Write a wrapper function COPY-ACC-WRAPPER that consumes a string ;; (str) and a number (n) and produces the result of appending the ;; string to itself n times. Use a single call to the COPY-ACC ;; function you wrote above. ;; Contract: (copy-acc string number) -> string ;; Header: (define copy-acc-wrapper (lambda (str n) ... )) ;; Purpose: produce string with n copies of str. ;; Pre-function tests: (check-expect (copy-acc-wrapper "la" 4) "lalalala") (check-expect (copy-acc-wrapper "la" 1) "la") (check-expect (copy-acc-wrapper "do" 2) "dodo") ;; Function: (define copy-acc-wrapper (lambda (str n) ;; body is just call to accumulator function (copy-acc str n ""))) ;; Post-function tests: (copy-acc-wrapper "me" 8) (copy-acc-wrapper "me" 0) ; The LOCAL special form is used to define variables and ; functions inside a function (see pages 65-68 of lecture ; notes pdf Part 4). ; ; One use of local is to define accumulator functions inside a ; wrapper function. ;