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)))