Download PDF Version
Total Page:16
File Type:pdf, Size:1020Kb
Part V Abstraction We've really been talking about abstraction all along. Whenever you ®nd yourself performing several similar computations, such as > (sentence 'she (word 'run 's)) (SHE RUNS) > (sentence 'she (word 'walk 's)) (SHE WALKS) > (sentence 'she (word 'program 's)) (SHE PROGRAMS) and you capture the similarity in a procedure (define (third-person verb) (sentence 'she (word verb 's))) you're abstracting the pattern of the computation by expressing it in a form that leaves out the particular verb in any one instance. In the preface we said that our approach to computer science is to teach you to think in larger chunks, so that you can ®t larger problems in your mind at once; ªabstractionº is the technical name for that chunking process. In this part of the book we take a closer look at two speci®c kinds of abstraction. One is data abstraction, which means the invention of new data types. The other is the implementation of higher-order functions, an important category of the same process abstraction of which third-person is a trivial example. 278 Until now we've used words and sentences as though they were part of the natural order of things. Now we'll discover that Scheme sentences exist only in our minds and take shape through the use of constructors and selectors (sentence, first, and so on) that we wrote. The implementation of sentences is based on a more fundamental data type called lists. Then we'll see how lists can be used to invent another in-our-minds data type, trees. (The technical term for an invented data type is an abstract data type.) You already know how higher-order functions can express many computational processes in a very compact form. Now we focus our attention on the higher-order procedures that implement those functions, exploring the mechanics by which we create these process abstractions. 279 17 Lists Suppose we're using Scheme to model an ice cream shop. We'll certainly need to know all the ¯avors that are available: (vanilla ginger strawberry lychee raspberry mocha) For example, here's a procedure that models the behavior of the salesperson when you place an order: (define (order flavor) (if (member? flavor '(vanilla ginger strawberry lychee raspberry mocha)) '(coming right up!) (se '(sorry we have no) flavor))) But what happens if we want to sell a ¯avor like ªroot beer fudge rippleº or ªultra chocolateº? We can't just put those words into a sentence of ¯avors, or our program will think that each word is a separate ¯avor. Beer ice cream doesn't sound very appealing. What we need is a way to express a collection of items, each of which is itself a collection, like this: (vanilla (ultra chocolate) (heath bar crunch) ginger (cherry garcia)) This is meant to represent ®ve ¯avors, two of which are named by single words, and the other three of which are named by sentences. Luckily for us, Scheme provides exactly this capability. The data structure we're using in this example is called a list. The difference between a sentence and a list is that the elements of a sentence must be words, whereas the elements of a list can be anything 281 at all: words, #t, procedures, or other lists. (A list that's an element of another list is called a sublist. We'll use the name structured list for a list that includes sublists.) Another way to think about the difference between sentences and lists is that the de®nition of ªlistº is self-referential, because a list can include lists as elements. The de®nition of ªsentenceº is not self-referential, because the elements of a sentence must be words. We'll see that the self-referential nature of recursive procedures is vitally important in coping with lists. Another example in which lists could be helpful is the pattern matcher. We used sentences to hold known-values databases, such as this one: (FRONT YOUR MOTHER ! BACK SHOULD KNOW !) This would be both easier for you to read and easier for programs to manipulate if we used list structure to indicate the grouping instead of exclamation points: ((FRONT (YOUR MOTHER)) (BACK (SHOULD KNOW))) We remarked when we introduced sentences that they're a feature we added to Scheme just for the sake of this book. Lists, by contrast, are at the core of what Lisp has been about from its beginning. (In fact the name ªLispº stands for ªLISt Processing.º) Selectors and Constructors When we introduced words and sentences we had to provide ways to take them apart, such as first, and ways to put them together, such as sentence. Now we'll tell you about the selectors and constructors for lists. The function to select the ®rst element of a list is called car.* The function to select the portion of a list containing all but the ®rst element is called cdr, which is * Don't even try to ®gure out a sensible reason for this name. It's a leftover bit of history from the ®rst computer on which Lisp was implemented. It stands for ªcontents of address registerº (at least that's what all the books say, although it's really the address portion of the accumulator register). Cdr, coming up in the next sentence, stands for ªcontents of decrement register.º The names seem silly in the Lisp context, but that's because the Lisp people used these register components in ways the computer designers didn't intend. Anyway, this is all very interesting to history buffs but irrelevant to our purposes. We're just showing off that one of us is actually old enough to remember these antique computers ®rst-hand. 282 Part V Abstraction pronounced ªcould-er.º These are analogous to first and butfirst for words and sentences. Of course, we can't extract pieces of a list that's empty, so we need a predicate that will check for an empty list. It's called null? and it returns #t for the empty list, #f for anything else. This is the list equivalent of empty? for words and sentences. There are two constructors for lists. The function list takes any number of arguments and returns a list with those arguments as its elements. > (list (+ 2 3) 'squash (= 2 2) (list 4 5) remainder 'zucchini) (5 SQUASH #T (4 5) #<PROCEDURE> ZUCCHINI) The other constructor, cons, is used when you already have a list and you want to add one new element. Cons takes two arguments, an element and a list (in that order), and returns a new list whose car is the ®rst argument and whose cdr is the second. > (cons 'for '(no one)) (FOR NO ONE) > (cons 'julia '()) (JULIA) There is also a function that combines the elements of two or more lists into a larger list: > (append '(get back) '(the word)) (GET BACK THE WORD) It's important that you understand how list, cons, and append differ from each other: > (list '(i am) '(the walrus)) ((I AM) (THE WALRUS)) > (cons '(i am) '(the walrus)) ((I AM) THE WALRUS) > (append '(i am) '(the walrus)) (I AM THE WALRUS) When list is invoked with two arguments, it considers them to be two proposed elements for a new two-element list. List doesn't care whether the arguments are themselves lists, words, or anything else; it just creates a new list whose elements are the arguments. In this case, it ends up with a list of two lists. Chapter 17 Lists 283 Cons requires that its second argument be a list.* Cons will extend that list to form a new list, one element longer than the original; the ®rst element of the resulting list comes from the ®rst argument to cons. In other words, when you pass cons two arguments, you get back a list whose car is the ®rst argument to cons and whose cdr is the second argument. Thus, in this example, the three elements of the returned list consist of the ®rst argument as one single element, followed by the elements of the second argument (in this case, two words). (You may be wondering why anyone would want to use such a strange constructor instead of list. The answer has to do with recursive procedures, but hang on for a few paragraphs and we'll show you an example, which will help more than any explanation we could give in English.) Finally, append of two arguments uses the elements of both arguments as elements of its return value. Pictorially, list creates a list whose elements are the arguments: list ( ) Cons creates an extension of its second argument with one new element: cons ( ) ( ) * This is not the whole story. See the ªpitfallsº section for a slightly expanded version. 284 Part V Abstraction Append creates a list whose elements are the elements of the arguments, which must be lists: append ( ) ( ) ( ) Programming with Lists (define (praise flavors) (if (null? flavors) '() (cons (se (car flavors) '(is delicious)) (praise (cdr flavors))))) > (praise '(ginger (ultra chocolate) lychee (rum raisin))) ((GINGER IS DELICIOUS) (ULTRA CHOCOLATE IS DELICIOUS) (LYCHEE IS DELICIOUS) (RUM RAISIN IS DELICIOUS)) In this example our result is a list of sentences. That is, the result is a list that includes smaller lists as elements, but each of these smaller lists is a sentence, in which only words are allowed. That's why we used the constructor cons for the overall list, but se for each sentence within the list. This is the example worth a thousand words that we promised, to show why cons is useful.