CSC151 2009F, Class 31: Naming Local Procedures Admin: * Yes, exam 2 will be distributed in class tomorrow. * I will not be available for office hours on Wednesday. I should be available via email later in the day. * Reading for Wednesday: Numeric Recursion. * EC for attending or participating in (but not running) drag show * Double EC for going to Carroll to support the soccer team in the playoffs * EC for hate crime response forums this and next Thursday * EC for Thursday 7pm concert: Flute in the molecules that matter * EC for tonight's flag ceremony and panel discussion on nationality Overview: * Husk and kernel programming. * Why have local procedures. * Creating local recursive procedures with letrec. * Creating local recursive procedures with named let. * Lab. What was the topic of yesterday's class? * Escape! * Topic: Making your own error messages when things go wrong. * The error procedure "escapes" from the flow of computation. Normal strategy: Take a procedure that works, and add lots of error checking so that the client better understands why things go wrong. (define index-of (lambda (val lst) (if (equal? val (car lst)) 0 (+ 1 (index-of val (cdr lst)))))) (define index-of (lambda (val lst) (when (null? lst) (error "Cannot find the value" val)) (if (equal? val (car lst)) 0 (+ 1 (index-of val (cdr lst)))))) In some of the cases in which we were checking preconditions, we checked each value in a list. (when (not (all-drawings? drawings)) (error "There seems to be something other than a drawing in" drawings)) A problem with this strategy when doing recursion. Suppose we have a list of 20 drawings that we're processing. We check to make sure that they're all drawings. We process the first element. We recurse on the remaining 19 elements. We check to make sure that they're all drawings. UNNECESSARY WORK. When writing a recursive procedure that also has precondition checking, separate the two. We call that separation the husk and kernel style to procedure writing. * Idea: The husk checks the preconditions ONCE and then calls the kerenl. * The kernel then does the recursion, without checking preconditions. (define proc (lambda (params) (cond ((precondition-test) (error)) ((precondition-test) (error)) ((precondition-test) (error)) ((precondition-test) (error)) (else (kernel params))))) (define kernel (lambda (params) (if (base-case-test) base-case (combine (car params) (kernel (cdr params)))))) A problem: Since the kernel is a separate procedure, other people can call it without checking the preconditions. That's bad. What do you normally do when you want to restrict access to a value? We use local bindings (define proc (let ((kernel ...)) (lambda (params) ...))) let doesn't work for locally binding recursive procedures (define last-kernel (lambda (lst) (if (null? (cdr lst)) (car lst) (last-kernel (cdr lst))))) (define last (lambda (lst) (cond ((null? lst) (error "Cannot find the last element of an empty list")) ((not (list? lst)) (error "Cannot find the last element of a non-list")) (else (last-kernel lst))))) MAKE THE KERNEL LOCAL (define last (let ((last-kernel (lambda (lst) (if (null? (cdr lst)) (car lst) (last-kernel (cdr lst)))))) (lambda (lst) (cond ((null? lst) (error "Cannot find the last element of an empty list")) ((not (list? lst)) (error "Cannot find the last element of a non-list")) (else (last-kernel lst)))))) WHOOPS! let won't work with recursive procedures. Use letrec. (define last (letrec ((last-kernel (lambda (lst) (if (null? (cdr lst)) (car lst) (last-kernel (cdr lst)))))) (lambda (lst) (cond ((null? lst) (error "Cannot find the last element of an empty list")) ((not (list? lst)) (error "Cannot find the last element of a non-list")) (else (last-kernel lst))))))