Consider the following two programs from the exam: (define (get-character-names aloc) (cond [(empty? aloc) empty] [else (cons (character-name (first aloc)) (get-character-names (rest aloc)))])) (define (all-lose-life aloc) (cond [(empty? aloc) empty] [else (cons (make-character (character-name (first aloc)) (- 1 (character-lives (first aloc))) (character-moves (first aloc))) (all-lose=life (rest aloc)))])) Can you write a function that abstracts over these two programs? We proceed as before, locating the spots where the programs differ and adding parameters to account for these spots: (define (process-characters ... aloc) (cond [(empty? aloc) empty] [else (cons ... (first aloc) (process-characters (rest aloc)))])) Using process-one to fill in the ... in the cons yields the following program: (define (process-characters process-one aloc) (cond [(empty? aloc) empty] [else (cons (process-one (first aloc)) (process-characters process-one (rest aloc)))])) Process-characters takes a list of characters and a function that consumes characters. It produces a new list where we've applied the function to each character in the input list. For example, we could write get-character-names using process-characters as follows: (define (get-character-names aloc) (process-characters character-name aloc)) What should we write as the contract for process-characters? Let's look first at the outputs. When we write get-character-names, we produce a list-of-symbols. When we write all-lose-life, we produce a list-of-characters. How do we write the data definition for the output list? One option is to create data definitions for char-or-sym and list-of-char-or-sym. What's wrong with this option? First, list-of-char-or-sym allows symbols and characters in the same list. Our examples use lists of only one type of data. Second, this restricts keep to processing only lists containing symbols or characters. What if we wanted to use process-characters to produce a list of numbers? We don't want to have to change the contract every time we find a new use for process-characters. Let's look at the two data definitions for lists of symbols and list of characters: A list-of-symbols is either - empty, or - (cons S los) where S is a symbol and los is a list-of-symbols A list-of-characters is either - empty, or - (cons C loc) where C is a character and loc is a list-of-characters Where do these definitions differ? Only in the type of the first argument to cons. We can share the common structure of these data definitions by writing a new data definition that uses a variable for the type of the argument. A list-of-alpha is either - empty, or - (cons A loa), where A is an alpha and loa is a list-of-alpha We'll use the abbreviation (listof alpha) for a list containing elements of type alpha. Thus list-of-symbol is (listof symbol) and list-of-characters is (listof character). We will use names of greek letters to denote variables over a type. Thus. (listof alpha) means we have a list of elements of type alpha, but that we don't know what alpha is. Now, we can write a contract for process-characters. ;; process-characters : ;; (character -> alpha) (listof character) -> (listof alpha) Consider the program double-all, which takes a list of numbers and returns a list of numbers where each number in the input list has been doubled: (define (double-all alon) (cond [(empty? alon) empty] [else (cons (* 2 (first alon)) (double-all (rest alon)))])) How does double-all compare to process-characters? ;; process-characters : ;; (character -> alpha) (listof character) -> (listof alpha) ;; applies the argument function to each character in the input list (define (process-characters process-one aloc) (cond [(empty? aloc) empty] [else (cons (process-one (first aloc)) (process-characters process-one (rest aloc)))])) Both programs are quite similar: they consume a list and produce a new list formed by applying an operation to each element in the input list. Can we write an abstract version of these two functions? Yes. Let's call it map. ;; map : (beta -> alpha) (listof beta) -> (listof alpha) ;; applies the input function to every element of the input list (define (map f l) (cond [(empty? l) empty] [else (cons (f (first l)) (map f (rest l)))])) Mapping in such a common operation in programming, that map is actually built-in to DrScheme. Here is double-all written in terms of map: (define (double-all alon) (local ((define (double n) (* 2 n))) (map double alon))) Now, consider again our keep program from Monday: ;; 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 consider the program remove-dead from the first exam. ;; remove-dead : loc -> loc ;; removes all characters with less than one life from the list (define (remove-dead aloc) (cond [(empty? aloc) empty] [else (cond [(>= (character-lives (first aloc)) 1) (cons (first aloc) (remove-dead (rest aloc)))] [else (remove-dead (rest aloc))])])) Can we write an abstract program that covers both keep and remove-dead? Keep is actually close. In fact, we could write remove-dead by doing the following: (define (remove-dead aloc) (local ((define (keep-character? a-c) (>= (character-lives a-c) 1))) (keep keep-character? aloc))) There's one problem with this however. The contract for keep requires a function from num to bool as the first argument. We are passing a function from character to bool. Clearly, we should be able to use keep to write remove-dead. How do we write the contract for keep to allow both uses? ;; keep : (alpha -> boolean) (listof alpha) -> (listof alpha) Keep is such a useful function that it is actually built-in in DrScheme. It is called filter. Look again at the new definition of double-all. When we first discussed local, we said that we wanted to use it in two cases: to avoid computing values more than once, and to make complicated expressions more readable. In the final definition of double-all, the local function double appears only once and is quite simple; thus, it doesn't really require its own name. Can we write double-all without using local? In order to do this, we need some way to write programs without giving them names. So far, we only know how to write programs using define. Scheme provides a keyword with which one can write un-named programs. That keyword is called lambda. Here's an example of how we could write double without naming it: Named Unnamed (define (double n) (lambda (n) (* 2 n)) (* 2 n)) Using the unnamed version, we could rewrite double-all as follows: (define (double-all alon) (map (lambda (n) (* 2 n)) alon)) Lambda is very useful because it lets us refrain from introducing unnecessary names in programs. You can think of lambda as a shorthand for (local ((define ...) ...) that is useful if you use the locally defined program only once.