;; 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 ??. ; PRE-FUNCTION TESTS: ; (check-expect (facty-acc 5 ?) 120) ; (check-expect (facty-acc 0 ?) 1) ; (check-expect (facty-acc 1 ?) 1) ; FUNCTION: ; (define facty-acc ; (lambda (num acc) ; ...)) ; POST-FUNCTION DISPLAYS: ; (printf "(facty-acc 1 ?) => ~a~%" (facty-acc 1 ?)) ; (printf "(facty-acc 5 ?) => ~a~%" (facty-acc 5 ?)) ; ; 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. ; ; 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 a 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 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-whole-number) -> non-neg-whole-number ; 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 nat-num nat-num) -> nat-num ; HEADER: (define sum-acc (lambda (num acc) ... )) ; PURPOSE: Compute the sum of the numbers from 0 to num. ; ; PRE-FUNCTION TESTS: ; (check-expect (sum-acc 5 ?) 15) ; (check-expect (sum-acc 0 ?) 0) ; (check-expect (sum-acc 1 ?) 1) ; ; ; FUNCTION: ; (define sum-acc ; (lambda (num acc) ; ...)) ; ; ; POST-FUNCTION TESTS: ; (printf "(sum-acc 5 ?) => ~a~%" (sum-acc 5 ?)) ; (printf "(sum-acc 8 ?) => ~a~%" (sum-acc 8 ?)) ; (printf "(sum-acc 2 ?) => ~a~%" (sum-acc 2 ?)) ; ; ; WRAPPER FUNCTIONS: ; ; Wrapper functions let us avoid giving the initial value of the ; accumulator as an argument by calling the accumulator function ; from within a . ; ; A wrapper function contains only a call to an accumulator function ; using the base case (stopping) value as an argument. ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 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 ;; Contract: (copy string non-negative-integer) -> string ;; Header: (define copy (lambda (str num) ...)) ;; Purpose: produce string with num copies of str, each separated by a space. ;; Pre-function tests: ; (check-expect (copy "la" 4) "la la la la ") ; (check-expect (copy "la" 1) "la ") ; (check-expect (copy "do" 2) "do do ") ;; Function: ; (define copy ; (lambda (str n) ; ... ; )) ;; Post-function printfs: ; (printf "(copy \"me\" 8) => ~a~%" (copy "me" 8)) ; (printf "(copy \"joy\" 3) => ~a~%" (copy "joy" 3)) ;; What if we wanted no space after the last copy of str? How could ;; we implement this change in the function? ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 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, followed by a space, n times. ;; Contract: (copy-acc string non-negative-integer string) -> string ;; Header: (define copy-acc (lambda (str n acc) ...)) ;; Purpose: produce string with n copies of str, each separated by a space. ;; Pre-function tests: ; (check-expect (copy-acc "la" 4 "") "la la la la ") ; (check-expect (copy-acc "la" 1 "") "la ") ; (check-expect (copy-acc "do" 2 "") "do do ") ;; Function: ; (define copy-acc ; (lambda (str n acc) ; ... ; )) ;; Post-function tests: ; (printf "(copy-acc \"me\" 8 \"\") => ~a~%" (copy-acc "me" 8 "")) ; (pringf "(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 and a space 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, each separated by a space. ;; Pre-function tests: ; (check-expect (copy-acc-wrapper "la" 4) "la la la la ") ; (check-expect (copy-acc-wrapper "la" 1) "la ") ; (check-expect (copy-acc-wrapper "do" 2) "do do ") ;; Function: ; (define copy-acc-wrapper ; (lambda (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). ; ; One use of local is to define accumulator functions inside a ; wrapper function. ;