;; Assume you had the following two programs that extract boas from a
;; list of boas according to different criteria.  There's a lot of
;; similar code here, so we should be able to write a single function
;; that shares the common code.  How do we do that?

;; A boa is a (make-boa symbol number symbol)
(define-struct boa (name length eats))

;; extract-eats-mice : list-of-boa -> list-of-boa
;; extracts boas that eat mice from list of boas
(define (extract-eats-mice alob)
  (cond [(empty? alob) empty]
	[(cons? alob)
	 (cond [(symbol=? 'mice (boa-eats (first alob)))
		(cons (first alob) (extract-eats-mice (rest alob)))]
	       [else (extract-eats-mice (rest alob))])]))

;; extract-long : list-of-boa -> list-of-boa
;; extracts boas that have length longer than 10 from list of boas
(define (extract-long alob)
  (cond [(empty? alob) empty]
	[(cons? alob)
	 (cond [(> (boa-length (first alob)) 10)
		(cons (first alob) (extract-long (rest alob)))]
	       [else (extract-long (rest alob))])]))

;; First, identify the code that is different between the two
;; functions.  That code is the question part in the inner
;; cond--everything else is identical other than the names of the
;; functions.

;; Copy the common code into a new function, called extract-boas, and
;; add a parameter for the part that's different.  The part that's
;; different determines whether or not to keep the boa, so we'll call
;; that parameter keep?.  Note we also leave the (first alob) in that
;; line, since that was common to both functions.

;; extract-boas : list-of-boa -> list-of-boa
;; extracts boas that ...
(define (extract-boas keep? alob)
  (cond [(empty? alob) empty]
	[(cons? alob)
	 (cond [keep? (first alob) 
		(cons (first alob) (extract-boas keep? (rest alob)))]
	       [else (extract-boas keep? (rest alob))])]))

;; This code isn't syntactically correct, since the "question" part
;; has both the keep? parameter and the (first alob).  How do we
;; combine those?  Since both original functions used (first alob) in
;; keep?, it makes sense to think of keep? as a function that takes
;; (first alob) as an argument.

;; extract-boas : list-of-boa -> list-of-boa
;; extracts boas that ...
(define (extract-boas keep? alob)
  (cond [(empty? alob) empty]
	[(cons? alob)
	 (cond [(keep? (first alob))
		(cons (first alob) (extract-boas keep? (rest alob)))]
	       [else (extract-boas keep? (rest alob))])]))

;; We also need to update the contract and purpose to account for
;; keep?.  What is the type on keep?  It's a function that takes a boa
;; (we see that from how it is called) and returns a boolean (we see
;; that from where it is used -- in the question position of a cond).
;; We write this in the contract and update the purpose as follows:

;; extract-boas : (boa -> boolean) list-of-boa -> list-of-boa
;; extracts boas for which the given function returns true
(define (extract-boas keep? alob)
  (cond [(empty? alob) empty]
	[(cons? alob)
	 (cond [(keep? (first alob))
		(cons (first alob) (extract-boas keep? (rest alob)))]
	       [else (extract-boas keep? (rest alob))])]))

;; Now, we can write extract-eats-mice and extract-long using
;; extract-boas.  We start by using extract-boas in the body of
;; extract-eats-mice:

;; extract-eats-mice : list-of-boa -> list-of-boa
;; extracts boas that eat mice from list of boas
(define (extract-eats-mice alob)
  (extract-boas __________________ alob))

;; What do we send as the argument to keep?  One option is to send the
;; code that we yanked out.  This would yield

;; extract-eats-mice : list-of-boa -> list-of-boa
;; extracts boas that eat mice from list of boas
(define (extract-eats-mice alob)
  (extract-boas (symbol=? 'mice (boa-eats (first alob))) alob))

;; Try running this -- what happens?

;; This code isn't quite right: the symbol=? returns a boolean, but
;; you need to pass in an operator that _returns_ a boolean (not quite
;; the same thing).  We need to pass an operator.

;; Another way to see a problem here is to remember how Scheme
;; evaluates expressions: it evaluates arguments before it calls
;; operators.  In this example, Scheme checks the symbol=? on (first
;; alob) and passes that boolean result into keep?.  But we want
;; Scheme to run the check on EVERY boa in the list, not just the
;; first.  That also tells you something is wrong (as would testing
;; this on the empty list, which should be valid).

;; To fix this, we create a helper function that checks a single boa
;; and pass that along:

;; eats-mice? : boa -> boolean
;; determines whether a boa eats mice
(define (eats-mice? aboa)
  (symbol=? 'mice (boa-eats aboa)))

;; extract-eats-mice : list-of-boa -> list-of-boa
;; extracts boas that eat mice from list of boas
(define (extract-eats-mice alob)
  (extract-boas eats-mice? alob))

;; You could also do this with local as follows:

;; extract-eats-mice : list-of-boa -> list-of-boa
;; extracts boas that eat mice from list of boas
(define (extract-eats-mice alob)
  (local ((define (eats-mice? aboa) (symbol=? 'mice (boa-eats aboa))))
    (extract-boas eats-mice? alob)))

;; Similarly, we can define extract-long using extract-boas

;; extract-long : list-of-boa -> list-of-boa
;; extracts boas that have length longer than 10 from list of boas
(define (extract-long alob)
  (local ((define (long? aboa) (> (boa-length aboa) 10)))
    (extract-boas long? alob)))

;; So, the final version of the reworked code consists of the
;; following three functions:

;; extract-boas : (boa -> boolean) list-of-boa -> list-of-boa
;; extracts boas for which the given function returns true
(define (extract-boas keep? alob)
  (cond [(empty? alob) empty]
	[(cons? alob)
	 (cond [(keep? (first alob))
		(cons (first alob) (extract-boas keep? (rest alob)))]
	       [else (extract-boas keep? (rest alob))])]))

;; extract-eats-mice : list-of-boa -> list-of-boa
;; extracts boas that eat mice from list of boas
(define (extract-eats-mice alob)
  (local ((define (eats-mice? aboa) (symbol=? 'mice (boa-eats aboa))))
    (extract-boas eats-mice? alob)))

;; extract-long : list-of-boa -> list-of-boa
;; extracts boas that have length longer than 10 from list of boas
(define (extract-long alob)
  (local ((define (long? aboa) (> (boa-length aboa) 10)))
    (extract-boas long? alob)))

;; The appeal of the version that sent in the symbol=? expression was
;; that it seemed a bit easier--you didn't need the overhead of the
;; local, define, etc.  At the end of class, someone asked whether we
;; could avoid the local/define part while making this code compact.
;; Yes we can.  Tune in tomorrow.