Consider a simple function that consumes a list of numbers and produces a list of numbers. The numbers in the returned list are all of the numbers from the original list that are less than 5. ;; keep-lt-5 : lon -> lon ;; keeps all input numbers that are less than 5 (define (keep-lt-5 alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(< (first alon) 5) (cons (first alon) (keep-lt-5 (rest alon)))] [else (keep-lt-5 (rest alon))])])) We could write a similar program to keep all numbers that are less than 9: ;; keep-lt-9 : lon -> lon ;; keeps all input numbers that are less than 9 (define (keep-lt-9 alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(< (first alon) 9) (cons (first alon) (keep-lt-9 (rest alon)))] [else (keep-lt-9 (rest alon))])])) Notice how much code is common to these two functions. How could we write one function that captures all of the common code? ;; keep-lt : num lon -> lon ;; keeps all input numbers that are less than the given number (define (keep-lt num alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(< (first alon) num) (cons (first alon) (keep-lt num (rest alon)))] [else (keep-lt num (rest alon))])])) Notice that num never changes. We can use local to keep from passing it around. ;; keep-lt : num lon -> lon ;; keeps all input numbers that are less than the given number (define (keep-lt num alon) (local [(define (filter-lt a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cond [(< (first a-lon) num) (cons (first a-lon) (filter-lt (rest a-lon)))] [else (filter-lt (rest a-lon))])]))] (filter-lt alon))) Now, we can use keep-lt as a helper to define keep-lt-5 and keep-lt-9. (define (keep-lt-5 alon) (keep-lt 5 alon)) (define (keep-lt-9 alon) (keep-lt 9 alon)) Now, consider the following two programs: ;; keep-lt-5 : lon -> lon ;; keeps all input numbers that are less than 5 (define (keep-lt-5 alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(< (first alon) 5) (cons (first alon) (keep-lt-5 (rest alon)))] [else (keep-lt-5 (rest alon))])])) ;; keep-gt-5 : lon -> lon ;; keeps all input numbers that are greater than 5 (define (keep-gt-5 alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(> (first alon) 5) (cons (first alon) (keep-gt-5 (rest alon)))] [else (keep-gt-5 (rest alon))])])) Where do these two pieces of code differ? Only in the comparison operator (and in the name of the function, but from scoping lecture, know that difference is irrelevant). How can we reuse common code here? Previously, we made the number to compare to a parameter. We can do the same with the comparison operator: ;; keep-rel-5 : (num num -> num) lon -> lon ;; keeps all numbers that have the relation to 5 given by the argument ;; function (define (keep-rel-5 rel-op alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(rel-op (first alon) 5) (cons (first alon) (keep-rel-5 rel-op (rest alon)))] [else (keep-rel-5 rel-op (rest alon))])])) (define (keep-lt-5 alon) (keep-rel-5 < alon)) As before, we can use local to avoid passing around rel-op. ;; keep-rel-5 : (num num -> num) lon -> lon ;; keeps all numbers that have the relation to 5 given by the argument ;; function (define (keep-rel rel-op alon) (local [(define (filter-rel a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cond [(rel-op (first a-lon) 5) (cons (first a-lon) (filter-rel (rest a-lon)))] [else (filter-rel (rest a-lon))])]))] (filter-rel alon))) (define (keep-lt-5 alon) (keep-rel < alon)) What if we wanted to keep everything larger than 9? If we change keep-rel to also take the number to compare to, we can reuse keep-rel for this purpose too: ;; keep-rel : (num num -> num) num lon -> lon ;; keeps all numbers in the input list that have the relation to the ;; input number as given by the function argument (define (keep-rel rel-op num alon) (local [(define (filter-rel a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cond [(rel-op (first a-lon) num) (cons (first a-lon) (filter-rel (rest a-lon)))] [else (filter-rel (rest a-lon))])]))] (filter-rel alon))) (define (keep-gt-9 alon) (keep-rel > 9 alon)) Now, write a program to keep all numbers between 5 and 9 (inclusive): (define (keep-bet-5-9 alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(and (>= (first alon) 5) (<= (first alon) 9)) (cons (first alon) (keep-bet-5-9 (rest alon)))] [else (keep-bet-5-9 (rest alon))])])) By now, we know that we should really use a helper function to determine whether a number is between 5 and 9: (define (bet-5-9? num) (and (>= (first alon) 5) (<= (first alon) 9))) (define (keep-bet-5-9 alon) (cond [(empty? alon) empty] [(cons? alon) (cond [(bet-5-9? (first alon)) (cons (first alon) (keep-bet-5-9 (rest alon)))] [else (keep-bet-5-9 (rest alon))])])) What if we wanted to change the range of numbers to keep? We could edit the helper function, but what if need bet-5-9? for some other part of our program? We could write another helper function, but then we would need a new copy of keep-be-5-9. How can we write keep-bet-5-9 so that we can use it on arbitrary ranges of numbers? ;; bet? : num num num -> boolean ;; determines whether the third number lies between the first two ;; numbers. (define (bet? lower upper num) (and (>= num lower) (<= num upper))) ;; keep-bet : num num lon -> lon ;; keeps all numbers between the given numbers (define (keep-bet lower upper alon) (local [(define (filter-bet a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cond [(bet? lower upper (first a-lon)) (cons (first a-lon) (filter-bet (rest a-lon)))] [else (filter-bet (rest a-lon))])]))] (filter-bet alon))) (define (keep-bet-5-9 alon) (keep-bet 5 9 alon)) Look at the definitions of keep-bet and keep-rel. They look fairly similar with the exception of their parameters and the first case in the inner cond, which checks whether or not to keep the first number on the list. The differences between the parameters are for the different tests performed in the cond, so the different cond tests is the main difference between these two programs. How do we write one program to capture all of the common code? We start by copying down all the information that is common to both programs, leaving ... in places where the programs differ: (define (keep ... alon) (local [(define (filter a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cond [(... (first a-lon)) (cons (first a-lon) (filter (rest a-lon)))] [else (filter (rest a-lon))])]))] (filter alon))) Now, we just need to fill in the gaps. There is one gap in the body of the program, inside of the definition of filter. The missing information is the function that tells us whether or not to keep the first element on the list. We therefore make up a name for that function and add it as a parameter to keep: ;; keep : (num -> boolean) lon -> lon ;; keeps all numbers for which keep-elt? returns true (define (keep keep-elt? alon) (local [(define (filter a-lon) (cond [(empty? a-lon) empty] [(cons? a-lon) (cond [(keep-elt? (first a-lon)) (cons (first a-lon) (filter (rest a-lon)))] [else (filter (rest a-lon))])]))] (filter alon))) Now, we can write our keep-lt and keep-bet programs by passing in appropriate functions as the keep-elt? argument. (define (keep-lt-5 alon) (local [(define (lt-5? num) (< num 5))] (keep lt-5? alon))) (define (keep-bet-5-9 alon) (local [(define (bet-5-9? num) (bet? 5 9 num))] (keep bet-5-9? alon))) (define (keep-bet-4-7 alon) (local [(define (bet-4-7? num) (bet? 4 7 num))] (keep bet-4-7? alon))) Notice that keep-bet-5-9 and keep-bet-4-7 are very similar. The general version appears as follows: (define (keep-bet lower upper alon) (local [(define (bet-bounds? num) (bet? lower upper num))] (keep bet-bounds? alon)))