;; 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.